Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
|
@ -0,0 +1,33 @@
|
|||
# Basic data types for Migrate.
|
||||
|
||||
migrate_plugin:
|
||||
type: mapping
|
||||
mapping:
|
||||
plugin:
|
||||
type: string
|
||||
label: 'Plugin'
|
||||
|
||||
migrate_destination:
|
||||
type: migrate_plugin
|
||||
label: 'Destination'
|
||||
|
||||
migrate_source:
|
||||
type: migrate_plugin
|
||||
label: 'Source'
|
||||
mapping:
|
||||
constants:
|
||||
type: ignore
|
||||
label: 'Constants'
|
||||
|
||||
# Base schema for migrate source plugins that extend
|
||||
# \Drupal\migrate\Plugin\migrate\source\SqlBase.
|
||||
migrate_source_sql:
|
||||
type: migrate_source
|
||||
mapping:
|
||||
target:
|
||||
type: string
|
||||
label: 'The migration database target'
|
||||
|
||||
migrate_load:
|
||||
type: migrate_plugin
|
||||
label: 'Load'
|
|
@ -0,0 +1,34 @@
|
|||
# Schema for the migrate destination plugins.
|
||||
|
||||
migrate.destination.*:
|
||||
type: migrate_destination
|
||||
label: 'Default destination'
|
||||
mapping:
|
||||
no_stub:
|
||||
type: boolean
|
||||
label: 'Whether stubbing is allowed.'
|
||||
default: false
|
||||
|
||||
migrate.destination.config:
|
||||
type: migrate_destination
|
||||
label: 'Config'
|
||||
mapping:
|
||||
config_name:
|
||||
type: string
|
||||
label: 'Configuration name'
|
||||
|
||||
migrate.destination.entity:user:
|
||||
type: migrate_destination
|
||||
label: 'User'
|
||||
mapping:
|
||||
md5_passwords:
|
||||
type: boolean
|
||||
label: 'Passwords'
|
||||
|
||||
migrate.destination.entity:file:
|
||||
type: migrate_destination
|
||||
label: 'Picture'
|
||||
mapping:
|
||||
source_path_property:
|
||||
type: string
|
||||
label: 'Source path'
|
21
core/modules/migrate/config/schema/migrate.load.schema.yml
Normal file
21
core/modules/migrate/config/schema/migrate.load.schema.yml
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Schema for the migrate load plugins.
|
||||
|
||||
migrate.load.*:
|
||||
type: migrate_load
|
||||
label: 'Default load'
|
||||
|
||||
migrate.load.drupal_entity:
|
||||
type: migrate_load
|
||||
label: 'Default source'
|
||||
mapping:
|
||||
bundle_migration:
|
||||
type: string
|
||||
label: 'Bundle migration'
|
||||
|
||||
migrate.load.d6_term_node:
|
||||
type: migrate_load
|
||||
label: 'Default source'
|
||||
mapping:
|
||||
bundle_migration:
|
||||
type: string
|
||||
label: 'Bundle migration'
|
46
core/modules/migrate/config/schema/migrate.schema.yml
Normal file
46
core/modules/migrate/config/schema/migrate.schema.yml
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Schema for the configuration files of the Migrate module.
|
||||
|
||||
migrate.migration.*:
|
||||
type: config_entity
|
||||
label: 'Migration'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
migration_tags:
|
||||
type: sequence
|
||||
label: 'Migration Tags'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Tag'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
load:
|
||||
type: migrate.load.[plugin]
|
||||
label: 'Source'
|
||||
source:
|
||||
type: migrate.source.[plugin]
|
||||
label: 'Source'
|
||||
process:
|
||||
type: ignore
|
||||
label: 'Process'
|
||||
destination:
|
||||
type: migrate.destination.[plugin]
|
||||
label: 'Destination'
|
||||
migration_dependencies:
|
||||
type: mapping
|
||||
label: 'Dependencies'
|
||||
mapping:
|
||||
required:
|
||||
type: sequence
|
||||
label: 'Required dependencies'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Dependency'
|
||||
optional:
|
||||
type: sequence
|
||||
label: 'Optional dependencies'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Dependency'
|
13
core/modules/migrate/config/schema/migrate.source.schema.yml
Normal file
13
core/modules/migrate/config/schema/migrate.source.schema.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Schema for the migrate source plugins.
|
||||
|
||||
migrate.source.*:
|
||||
type: migrate_source
|
||||
label: 'Default source'
|
||||
|
||||
migrate.source.empty:
|
||||
type: migrate_source_sql
|
||||
label: 'Empty source'
|
||||
mapping:
|
||||
provider:
|
||||
type: string
|
||||
label: 'Provider'
|
131
core/modules/migrate/migrate.api.php
Normal file
131
core/modules/migrate/migrate.api.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Migrate module.
|
||||
*/
|
||||
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrateSourceInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @defgroup migration Migration API
|
||||
* @{
|
||||
* Overview of the Migration API, which migrates data into Drupal.
|
||||
*
|
||||
* @section overview Overview of migration
|
||||
* Migration is an
|
||||
* @link http://wikipedia.org/wiki/Extract,_transform,_load Extract, Transform, Load @endlink
|
||||
* (ETL) process. For historical reasons, in the Drupal migration tool the
|
||||
* extract phase is called "source", the transform phase is called "process",
|
||||
* and the load phase is called "destination".
|
||||
*
|
||||
* Source, process, and destination phases are each provided by plugins. Source
|
||||
* plugins extract data from a data source in "rows", containing "properties".
|
||||
* Each row is handed off to one or more series of process plugins, where each
|
||||
* series operates to transform the row data into one result property. After all
|
||||
* the properties are processed, the resulting row is handed off to a
|
||||
* destination plugin, which saves the data.
|
||||
*
|
||||
* The Migrate module provides process plugins for common operations (setting
|
||||
* default values, mapping values, etc.), and destination plugins for Drupal
|
||||
* core objects (configuration, entity, URL alias, etc.). The Migrate Drupal
|
||||
* module provides source plugins to extract data from various versions of
|
||||
* Drupal. Custom and contributed modules can provide additional plugins; see
|
||||
* the @link plugin_api Plugin API topic @endlink for generic information about
|
||||
* providing plugins, and sections below for details about the plugin types.
|
||||
*
|
||||
* The configuration of migrations is stored in configuration entities, which
|
||||
* list the IDs and configurations of the plugins that are involved. See
|
||||
* @ref sec_entity below for details. To migrate an entire site, you'll need to
|
||||
* create a migration manifest; see @ref sec_manifest for details.
|
||||
*
|
||||
* https://www.drupal.org/node/2127611 has more complete information on the
|
||||
* Migration API, including information on load plugins, which are only used
|
||||
* in Drupal 6 migration.
|
||||
*
|
||||
* @section sec_source Source plugins
|
||||
* Migration 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\MigratePluginManager class.
|
||||
*
|
||||
* @section sec_process Process plugins
|
||||
* Migration 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.
|
||||
*
|
||||
* @section sec_destination Destination plugins
|
||||
* Migration 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
|
||||
* are managed by the
|
||||
* \Drupal\migrate\Plugin\MigrateDestinationPluginManager class.
|
||||
*
|
||||
* @section sec_entity Migration configuration entities
|
||||
* The definition of how to migrate each type of data is stored in configuration
|
||||
* entities. The migration configuration entity class is
|
||||
* \Drupal\migrate\Entity\Migration, with interface
|
||||
* \Drupal\migrate\Entity\MigrationInterface; the configuration schema can be
|
||||
* found in the migrate.schema.yml file. Migration configuration consists of IDs
|
||||
* and configuration for the source, process, and destination plugins, as well
|
||||
* as information on dependencies. Process configuration consists of sections,
|
||||
* each of which defines the series of process plugins needed for one
|
||||
* destination property. You can find examples of migration configuration files
|
||||
* in the core/modules/migrate_drupal/config/install directory.
|
||||
*
|
||||
* @section sec_manifest Migration manifests
|
||||
* You can run a migration with the "drush migrate-manifest" command, providing
|
||||
* a migration manifest file. This file lists the configuration names of the
|
||||
* migrations you want to execute, as well as any dependencies they have (you
|
||||
* can find these in the "migration_dependencies" sections of the individual
|
||||
* configuration files). For example, to migrate blocks from a Drupal 6 site,
|
||||
* you would list:
|
||||
* @code
|
||||
* # Migrate blocks from Drupal 6 to 8
|
||||
* - d6_filter_format
|
||||
* - d6_custom_block
|
||||
* - d6_block
|
||||
* @endcode
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allows adding data to a row before processing it.
|
||||
*
|
||||
* For example, filter module used to store filter format settings in the
|
||||
* variables table which now needs to be inside the filter format config
|
||||
* file. So, it needs to be added here.
|
||||
*
|
||||
* hook_migrate_MIGRATION_ID_prepare_row() is also available.
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
|
||||
if ($migration->id() == 'd6_filter_formats') {
|
||||
$value = $source->getDatabase()->query('SELECT value FROM {variable} WHERE name = :name', array(':name' => 'mymodule_filter_foo_' . $row->getSourceProperty('format')))->fetchField();
|
||||
if ($value) {
|
||||
$row->setSourceProperty('settings:mymodule:foo', unserialize($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
7
core/modules/migrate/migrate.info.yml
Normal file
7
core/modules/migrate/migrate.info.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: Migrate
|
||||
type: module
|
||||
description: 'Handles migrations'
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
;configure: admin/structure/migrate
|
22
core/modules/migrate/migrate.module
Normal file
22
core/modules/migrate/migrate.module
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides the Migrate API.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function migrate_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.migrate':
|
||||
$output = '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>';
|
||||
$output .= t('The Migrate module provides a framework for migrating data, usually from an external source into your site. It does not provide a user interface. For more information, see the <a href="!migrate">online documentation for the Migrate module</a>.', array('!migrate' => 'https://www.drupal.org/documentation/modules/migrate'));
|
||||
$output .= '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
22
core/modules/migrate/migrate.services.yml
Normal file
22
core/modules/migrate/migrate.services.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
services:
|
||||
cache.migrate:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
- { name: cache.bin }
|
||||
factory: cache_factory:get
|
||||
arguments: [migrate]
|
||||
plugin.manager.migrate.source:
|
||||
class: Drupal\migrate\Plugin\MigratePluginManager
|
||||
arguments: [source, '@container.namespaces', '@cache.discovery', '@module_handler', 'Drupal\migrate\Annotation\MigrateSource']
|
||||
plugin.manager.migrate.process:
|
||||
class: Drupal\migrate\Plugin\MigratePluginManager
|
||||
arguments: [process, '@container.namespaces', '@cache.discovery', '@module_handler', 'Drupal\migrate\Annotation\MigrateProcessPlugin']
|
||||
plugin.manager.migrate.destination:
|
||||
class: Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
arguments: [destination, '@container.namespaces', '@cache.discovery', '@module_handler', '@entity.manager']
|
||||
plugin.manager.migrate.id_map:
|
||||
class: Drupal\migrate\Plugin\MigratePluginManager
|
||||
arguments: [id_map, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
password_migrate:
|
||||
class: Drupal\migrate\MigratePassword
|
||||
arguments: ['@password_original']
|
60
core/modules/migrate/src/Annotation/MigrateDestination.php
Normal file
60
core/modules/migrate/src/Annotation/MigrateDestination.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Annotation\MigrateDestination.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a migration destination plugin annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\destination
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\migrate\Plugin\migrate\destination\UrlAlias
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class MigrateDestination extends Plugin {
|
||||
|
||||
/**
|
||||
* A unique identifier for the process plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Whether requirements are met.
|
||||
*
|
||||
* If TRUE and a 'provider' key is present in the annotation then the
|
||||
* default destination plugin manager will set this to FALSE if the
|
||||
* provider (module/theme) doesn't exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $requirements_met = TRUE;
|
||||
|
||||
/**
|
||||
* A class to make the plugin derivative aware.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator
|
||||
*/
|
||||
public $derivative;
|
||||
|
||||
}
|
52
core/modules/migrate/src/Annotation/MigrateProcessPlugin.php
Normal file
52
core/modules/migrate/src/Annotation/MigrateProcessPlugin.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Annotation\MigrateProcessPlugin.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a migration process plugin annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\process
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\migrate\Plugin\migrate\process\DefaultValue
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
* @see \Drupal\migrate\ProcessPluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class MigrateProcessPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* A unique identifier for the process plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Whether the plugin handles multiples itself.
|
||||
*
|
||||
* Typically these plugins will expect an array as input and iterate over it
|
||||
* themselves, changing the whole array. For example the 'iterator' and the
|
||||
* 'flatten' plugins. If the plugin only need to change a single value it
|
||||
* can skip setting this attribute and let
|
||||
* \Drupal\migrate\MigrateExecutable::processRow() handle the iteration.
|
||||
*
|
||||
* @var bool (optional)
|
||||
*/
|
||||
public $handle_multiples = FALSE;
|
||||
}
|
48
core/modules/migrate/src/Annotation/MigrateSource.php
Normal file
48
core/modules/migrate/src/Annotation/MigrateSource.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Annotation\MigrateSource.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a migration source plugin annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\source
|
||||
*
|
||||
* For a working example, check
|
||||
* \Drupal\migrate\Plugin\migrate\source\EmptySource
|
||||
* \Drupal\migrate_drupal\Plugin\migrate\source\UrlAlias
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class MigrateSource extends Plugin {
|
||||
|
||||
/**
|
||||
* A unique identifier for the process plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Whether requirements are met.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $requirements_met = TRUE;
|
||||
|
||||
}
|
537
core/modules/migrate/src/Entity/Migration.php
Normal file
537
core/modules/migrate/src/Entity/Migration.php
Normal file
|
@ -0,0 +1,537 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Entity\Migration.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Entity;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
|
||||
/**
|
||||
* Defines the Migration entity.
|
||||
*
|
||||
* The migration entity stores the information about a single migration, like
|
||||
* the source, process and destination plugins.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "migration",
|
||||
* label = @Translation("Migration"),
|
||||
* module = "migrate",
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\migrate\MigrationStorage"
|
||||
* },
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* "weight" = "weight"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Migration extends ConfigEntityBase implements MigrationInterface, RequirementsInterface {
|
||||
|
||||
/**
|
||||
* The migration ID (machine name).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The human-readable label for the migration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The plugin ID for the row.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $row;
|
||||
|
||||
/**
|
||||
* The source configuration, with at least a 'plugin' key.
|
||||
*
|
||||
* Used to initialize the $sourcePlugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* The source plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
*/
|
||||
protected $sourcePlugin;
|
||||
|
||||
/**
|
||||
* The configuration describing the process plugins.
|
||||
*
|
||||
* This is a strictly internal property and should not returned to calling
|
||||
* code, use getProcess() instead.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $process;
|
||||
|
||||
/**
|
||||
* The configuration describing the load plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $load;
|
||||
|
||||
/**
|
||||
* The cached process plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $processPlugins = [];
|
||||
|
||||
/**
|
||||
* The destination configuration, with at least a 'plugin' key.
|
||||
*
|
||||
* Used to initialize $destinationPlugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* The destination plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
*/
|
||||
protected $destinationPlugin;
|
||||
|
||||
/**
|
||||
* The identifier map data.
|
||||
*
|
||||
* Used to initialize $idMapPlugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $idMap = [];
|
||||
|
||||
/**
|
||||
* The identifier map.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
*/
|
||||
protected $idMapPlugin;
|
||||
|
||||
/**
|
||||
* The source identifiers.
|
||||
*
|
||||
* An array of source identifiers: the keys are the name of the properties,
|
||||
* the values are dependent on the ID map plugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIds = [];
|
||||
|
||||
/**
|
||||
* The destination identifiers.
|
||||
*
|
||||
* An array of destination identifiers: the keys are the name of the
|
||||
* properties, the values are dependent on the ID map plugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destinationIds = [];
|
||||
|
||||
/**
|
||||
* Information on the high water mark.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $highWaterProperty;
|
||||
|
||||
/**
|
||||
* Indicate whether the primary system of record for this migration is the
|
||||
* source, or the destination (Drupal). In the source case, migration of
|
||||
* an existing object will completely replace the Drupal object with data from
|
||||
* the source side. In the destination case, the existing Drupal object will
|
||||
* be loaded, then changes from the source applied; also, rollback will not be
|
||||
* supported.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $systemOfRecord = self::SOURCE;
|
||||
|
||||
/**
|
||||
* Specify value of source_row_status for current map row. Usually set by
|
||||
* MigrateFieldHandler implementations.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
*/
|
||||
protected $highWaterStorage;
|
||||
|
||||
/**
|
||||
* Track time of last import if TRUE.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $trackLastImported = FALSE;
|
||||
|
||||
/**
|
||||
* These migrations must be already executed before this migration can run.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $requirements = [];
|
||||
|
||||
/**
|
||||
* These migrations, if run, must be executed before this migration.
|
||||
*
|
||||
* These are different from the configuration dependencies. Migration
|
||||
* dependencies are only used to store relationships between migrations.
|
||||
*
|
||||
* The migration_dependencies value is structured like this:
|
||||
* @code
|
||||
* array(
|
||||
* 'required' => array(
|
||||
* // An array of migration IDs that must be run before this migration.
|
||||
* ),
|
||||
* 'optional' => array(
|
||||
* // An array of migration IDs that, if they exist, must be run before
|
||||
* // this migration.
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migration_dependencies = [];
|
||||
|
||||
/**
|
||||
* The migration's configuration dependencies.
|
||||
*
|
||||
* These store any dependencies on modules or other configuration (including
|
||||
* other migrations) that must be available before the migration can be
|
||||
* created.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourcePlugin() {
|
||||
if (!isset($this->sourcePlugin)) {
|
||||
$this->sourcePlugin = \Drupal::service('plugin.manager.migrate.source')->createInstance($this->source['plugin'], $this->source, $this);
|
||||
}
|
||||
return $this->sourcePlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProcessPlugins(array $process = NULL) {
|
||||
if (!isset($process)) {
|
||||
$process = $this->process;
|
||||
}
|
||||
$index = serialize($process);
|
||||
if (!isset($this->processPlugins[$index])) {
|
||||
$this->processPlugins[$index] = array();
|
||||
foreach ($this->getProcessNormalized($process) as $property => $configurations) {
|
||||
$this->processPlugins[$index][$property] = array();
|
||||
foreach ($configurations as $configuration) {
|
||||
if (isset($configuration['source'])) {
|
||||
$this->processPlugins[$index][$property][] = \Drupal::service('plugin.manager.migrate.process')->createInstance('get', $configuration, $this);
|
||||
}
|
||||
// Get is already handled.
|
||||
if ($configuration['plugin'] != 'get') {
|
||||
$this->processPlugins[$index][$property][] = \Drupal::service('plugin.manager.migrate.process')->createInstance($configuration['plugin'], $configuration, $this);
|
||||
}
|
||||
if (!$this->processPlugins[$index][$property]) {
|
||||
throw new MigrateException("Invalid process configuration for $property");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->processPlugins[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve shorthands into a list of plugin configurations.
|
||||
*
|
||||
* @param array $process
|
||||
* A process configuration array.
|
||||
*
|
||||
* @return array
|
||||
* The normalized process configuration.
|
||||
*/
|
||||
protected function getProcessNormalized(array $process) {
|
||||
$normalized_configurations = array();
|
||||
foreach ($process as $destination => $configuration) {
|
||||
if (is_string($configuration)) {
|
||||
$configuration = array(
|
||||
'plugin' => 'get',
|
||||
'source' => $configuration,
|
||||
);
|
||||
}
|
||||
if (isset($configuration['plugin'])) {
|
||||
$configuration = array($configuration);
|
||||
}
|
||||
$normalized_configurations[$destination] = $configuration;
|
||||
}
|
||||
return $normalized_configurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationPlugin($stub_being_requested = FALSE) {
|
||||
if (!isset($this->destinationPlugin)) {
|
||||
if ($stub_being_requested && !empty($this->destination['no_stub'])) {
|
||||
throw new MigrateSkipRowException;
|
||||
}
|
||||
$this->destinationPlugin = \Drupal::service('plugin.manager.migrate.destination')->createInstance($this->destination['plugin'], $this->destination, $this);
|
||||
}
|
||||
return $this->destinationPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIdMap() {
|
||||
if (!isset($this->idMapPlugin)) {
|
||||
$configuration = $this->idMap;
|
||||
$plugin = isset($configuration['plugin']) ? $configuration['plugin'] : 'sql';
|
||||
$this->idMapPlugin = \Drupal::service('plugin.manager.migrate.id_map')->createInstance($plugin, $configuration, $this);
|
||||
}
|
||||
return $this->idMapPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the high water storage object.
|
||||
*
|
||||
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
* The storage object.
|
||||
*/
|
||||
protected function getHighWaterStorage() {
|
||||
if (!isset($this->highWaterStorage)) {
|
||||
$this->highWaterStorage = \Drupal::keyValue('migrate:high_water');
|
||||
}
|
||||
return $this->highWaterStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighWater() {
|
||||
return $this->getHighWaterStorage()->get($this->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveHighWater($high_water) {
|
||||
$this->getHighWaterStorage()->set($this->id(), $high_water);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequirements() {
|
||||
// Check whether the current migration source and destination plugin
|
||||
// requirements are met or not.
|
||||
if ($this->getSourcePlugin() instanceof RequirementsInterface) {
|
||||
$this->getSourcePlugin()->checkRequirements();
|
||||
}
|
||||
if ($this->getDestinationPlugin() instanceof RequirementsInterface) {
|
||||
$this->getDestinationPlugin()->checkRequirements();
|
||||
}
|
||||
|
||||
/** @var \Drupal\migrate\Entity\MigrationInterface[] $required_migrations */
|
||||
$required_migrations = $this->getEntityManager()->getStorage('migration')->loadMultiple($this->requirements);
|
||||
|
||||
$missing_migrations = array_diff($this->requirements, array_keys($required_migrations));
|
||||
// Check if the dependencies are in good shape.
|
||||
foreach ($required_migrations as $migration_id => $required_migration) {
|
||||
if (!$required_migration->isComplete()) {
|
||||
$missing_migrations[] = $migration_id;
|
||||
}
|
||||
}
|
||||
if ($missing_migrations) {
|
||||
throw new RequirementsException(SafeMarkup::format('Missing migrations @requirements.', ['@requirements' => implode(', ', $missing_migrations)]), ['requirements' => $missing_migrations]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityManagerInterface
|
||||
* The entity manager.
|
||||
*/
|
||||
protected function getEntityManager() {
|
||||
if (!isset($this->entityManager)) {
|
||||
$this->entityManager = \Drupal::entityManager();
|
||||
}
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMigrationResult($result) {
|
||||
$migrate_result_store = \Drupal::keyValue('migrate_result');
|
||||
$migrate_result_store->set($this->id(), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMigrationResult() {
|
||||
$migrate_result_store = \Drupal::keyValue('migrate_result');
|
||||
return $migrate_result_store->get($this->id(), static::RESULT_INCOMPLETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isComplete() {
|
||||
return $this->getMigrationResult() === static::RESULT_COMPLETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
if ($property_name == 'source') {
|
||||
// Invalidate the source plugin.
|
||||
unset($this->sourcePlugin);
|
||||
}
|
||||
return parent::set($property_name, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProcess() {
|
||||
return $this->getProcessNormalized($this->process);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProcess(array $process) {
|
||||
$this->process = $process;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProcessOfProperty($property, $process_of_property) {
|
||||
$this->process[$property] = $process_of_property;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mergeProcessOfProperty($property, array $process_of_property) {
|
||||
// If we already have a process value then merge the incoming process array
|
||||
//otherwise simply set it.
|
||||
$current_process = $this->getProcess();
|
||||
if (isset($current_process[$property])) {
|
||||
$this->process = NestedArray::mergeDeepArray([$current_process, $this->getProcessNormalized([$property => $process_of_property])], TRUE);
|
||||
}
|
||||
else {
|
||||
$this->setProcessOfProperty($property, $process_of_property);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSystemOfRecord() {
|
||||
return $this->systemOfRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSystemOfRecord($system_of_record) {
|
||||
$this->systemOfRecord = $system_of_record;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTrackLastImported() {
|
||||
return $this->trackLastImported;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTrackLastImported($track_last_imported) {
|
||||
$this->trackLastImported = (bool) $track_last_imported;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMigrationDependencies() {
|
||||
return $this->migration_dependencies + ['required' => [], 'optional' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trustData() {
|
||||
// Migrations cannot be trusted since they are often written by hand and not
|
||||
// through a UI.
|
||||
$this->trustedData = FALSE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
parent::calculateDependencies();
|
||||
$this->calculatePluginDependencies($this->getSourcePlugin());
|
||||
$this->calculatePluginDependencies($this->getDestinationPlugin());
|
||||
// Add dependencies on required migration dependencies.
|
||||
foreach ($this->getMigrationDependencies()['required'] as $dependency) {
|
||||
$this->addDependency('config', $this->getEntityType()->getConfigPrefix() . '.' . $dependency);
|
||||
}
|
||||
|
||||
return $this->dependencies;
|
||||
}
|
||||
}
|
286
core/modules/migrate/src/Entity/MigrationInterface.php
Normal file
286
core/modules/migrate/src/Entity/MigrationInterface.php
Normal file
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Entity\MigrationInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Interface for migrations.
|
||||
*/
|
||||
interface MigrationInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* A constant used for systemOfRecord.
|
||||
*/
|
||||
const SOURCE = 'source';
|
||||
|
||||
/**
|
||||
* A constant used for systemOfRecord.
|
||||
*/
|
||||
const DESTINATION = 'destination';
|
||||
|
||||
/**
|
||||
* The migration is currently not running.
|
||||
*/
|
||||
const STATUS_IDLE = 0;
|
||||
|
||||
/**
|
||||
* The migration is currently importing.
|
||||
*/
|
||||
const STATUS_IMPORTING = 1;
|
||||
|
||||
/**
|
||||
* The migration is currently being rolled back.
|
||||
*/
|
||||
const STATUS_ROLLING_BACK = 2;
|
||||
|
||||
/**
|
||||
* The migration is being stopped.
|
||||
*/
|
||||
const STATUS_STOPPING = 3;
|
||||
|
||||
/**
|
||||
* The migration has been disabled.
|
||||
*/
|
||||
const STATUS_DISABLED = 4;
|
||||
|
||||
/**
|
||||
* Migration error.
|
||||
*/
|
||||
const MESSAGE_ERROR = 1;
|
||||
|
||||
/**
|
||||
* Migration warning.
|
||||
*/
|
||||
const MESSAGE_WARNING = 2;
|
||||
|
||||
/**
|
||||
* Migration notice.
|
||||
*/
|
||||
const MESSAGE_NOTICE = 3;
|
||||
|
||||
/**
|
||||
* Migration info.
|
||||
*/
|
||||
const MESSAGE_INFORMATIONAL = 4;
|
||||
|
||||
/**
|
||||
* All records have been processed.
|
||||
*/
|
||||
const RESULT_COMPLETED = 1;
|
||||
|
||||
/**
|
||||
* The process has stopped itself (e.g., the memory limit is approaching).
|
||||
*/
|
||||
const RESULT_INCOMPLETE = 2;
|
||||
|
||||
/**
|
||||
* The process was stopped externally (e.g., via drush migrate-stop).
|
||||
*/
|
||||
const RESULT_STOPPED = 3;
|
||||
|
||||
/**
|
||||
* The process had a fatal error.
|
||||
*/
|
||||
const RESULT_FAILED = 4;
|
||||
|
||||
/**
|
||||
* Dependencies are unfulfilled - skip the process.
|
||||
*/
|
||||
const RESULT_SKIPPED = 5;
|
||||
|
||||
/**
|
||||
* This migration is disabled, skipping.
|
||||
*/
|
||||
const RESULT_DISABLED = 6;
|
||||
|
||||
/**
|
||||
* Returns the initialized source plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* The source plugin.
|
||||
*/
|
||||
public function getSourcePlugin();
|
||||
|
||||
/**
|
||||
* Returns the process plugins.
|
||||
*
|
||||
* @param array $process
|
||||
* A process configuration array.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateProcessInterface[][]
|
||||
* An associative array. The keys are the destination property names. Values
|
||||
* are process pipelines. Each pipeline contains an array of plugins.
|
||||
*/
|
||||
public function getProcessPlugins(array $process = NULL);
|
||||
|
||||
/**
|
||||
* Returns the initialized destination plugin.
|
||||
*
|
||||
* @param bool $stub_being_requested
|
||||
* TRUE to indicate that this destination will be asked to construct a stub.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* The destination plugin.
|
||||
*/
|
||||
public function getDestinationPlugin($stub_being_requested = FALSE);
|
||||
|
||||
/**
|
||||
* Returns the initialized id_map plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
* The ID map.
|
||||
*/
|
||||
public function getIdMap();
|
||||
|
||||
/**
|
||||
* The current value of the high water mark.
|
||||
*
|
||||
* The high water mark defines a timestamp stating the time the import was last
|
||||
* run. If the mark is set, only content with a higher timestamp will be
|
||||
* imported.
|
||||
*
|
||||
* @return int
|
||||
* A Unix timestamp representing the high water mark.
|
||||
*/
|
||||
public function getHighWater();
|
||||
|
||||
/**
|
||||
* Save the new high water mark.
|
||||
*
|
||||
* @param int $high_water
|
||||
* The high water timestamp.
|
||||
*/
|
||||
public function saveHighWater($high_water);
|
||||
|
||||
/**
|
||||
* Check if this migration is complete.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this migration is complete otherwise FALSE.
|
||||
*/
|
||||
public function isComplete();
|
||||
|
||||
/**
|
||||
* Set the migration result.
|
||||
*
|
||||
* @param int $result
|
||||
* One of the RESULT_* constants.
|
||||
*/
|
||||
public function setMigrationResult($result);
|
||||
|
||||
/**
|
||||
* Get the current migration result.
|
||||
*
|
||||
* @return int
|
||||
* The current migration result. Defaults to RESULT_INCOMPLETE.
|
||||
*/
|
||||
public function getMigrationResult();
|
||||
|
||||
/**
|
||||
* Get the normalized process pipeline configuration describing the process
|
||||
* plugins.
|
||||
*
|
||||
* The process configuration is always normalized. All shorthand processing
|
||||
* will be expanded into their full representations.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2129651#get-shorthand
|
||||
*
|
||||
* @return array
|
||||
* The normalized configuration describing the process plugins.
|
||||
*/
|
||||
public function getProcess();
|
||||
|
||||
/**
|
||||
* Allows you to override the entire process configuration.
|
||||
*
|
||||
* @param array $process
|
||||
* The entire process pipeline configuration describing the process plugins.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProcess(array $process);
|
||||
|
||||
/**
|
||||
* Set the process pipeline configuration for an individual destination field.
|
||||
*
|
||||
* This method allows you to set the process pipeline configuration for a
|
||||
* single property within the full process pipeline configuration.
|
||||
*
|
||||
* @param string $property
|
||||
* The property of which to set the process pipeline configuration.
|
||||
* @param mixed $process_of_property
|
||||
* The process pipeline configuration to be set for this property.
|
||||
*
|
||||
* @return $this
|
||||
* The migration entity.
|
||||
*/
|
||||
public function setProcessOfProperty($property, $process_of_property);
|
||||
|
||||
/**
|
||||
* Merge the process pipeline configuration for a single property.
|
||||
*
|
||||
* @param string $property
|
||||
* The property of which to merge the passed in process pipeline
|
||||
* configuration.
|
||||
* @param array $process_of_property
|
||||
* The process pipeline configuration to be merged with the existing process
|
||||
* pipeline configuration.
|
||||
*
|
||||
* @return $this
|
||||
* The migration entity.
|
||||
*
|
||||
* @see Drupal\migrate_drupal\Plugin\migrate\load\LoadEntity::processLinkField().
|
||||
*/
|
||||
public function mergeProcessOfProperty($property, array $process_of_property);
|
||||
|
||||
/**
|
||||
* Get the current system of record of the migration.
|
||||
*
|
||||
* @return string
|
||||
* The current system of record of the migration.
|
||||
*/
|
||||
public function getSystemOfRecord();
|
||||
|
||||
/**
|
||||
* Set the system of record for the migration.
|
||||
*
|
||||
* @param string $system_of_record
|
||||
* The system of record of the migration.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSystemOfRecord($system_of_record);
|
||||
|
||||
/**
|
||||
* Checks if the migration should track time of last import.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the migration is tracking last import time.
|
||||
*/
|
||||
public function isTrackLastImported();
|
||||
|
||||
/**
|
||||
* Set if the migration should track time of last import.
|
||||
*
|
||||
* @param bool $track_last_imported
|
||||
* Boolean value to indicate if the migration should track last import time.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTrackLastImported($track_last_imported);
|
||||
|
||||
/**
|
||||
* Get the dependencies for this migration.
|
||||
*
|
||||
* @return array
|
||||
* The dependencies for this migrations.
|
||||
*/
|
||||
public function getMigrationDependencies();
|
||||
|
||||
}
|
70
core/modules/migrate/src/Exception/RequirementsException.php
Normal file
70
core/modules/migrate/src/Exception/RequirementsException.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Exception\RequirementsException.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Defines an
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\RequirementsInterface
|
||||
*/
|
||||
class RequirementsException extends \RuntimeException {
|
||||
|
||||
/**
|
||||
* The missing requirements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $requirements;
|
||||
|
||||
/**
|
||||
* Constructs a new RequirementsException instance.
|
||||
*
|
||||
* @param string $message
|
||||
* (optional) The Exception message to throw.
|
||||
* @param array $requirements
|
||||
* (optional) The missing requirements.
|
||||
* @param int $code
|
||||
* (optional) The Exception code.
|
||||
* @param \Exception $previous
|
||||
* (optional) The previous exception used for the exception chaining.
|
||||
*/
|
||||
public function __construct($message = "", array $requirements = [], $code = 0, Exception $previous = NULL) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->requirements = $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of requirements.
|
||||
*
|
||||
* @return array
|
||||
* The requirements.
|
||||
*/
|
||||
public function getRequirements() {
|
||||
return $this->requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requirements as a string.
|
||||
*
|
||||
* @return string
|
||||
* A formatted requirements string.
|
||||
*/
|
||||
public function getRequirementsString() {
|
||||
$output = '';
|
||||
foreach ($this->requirements as $requirement_type => $requirements) {
|
||||
foreach ($requirements as $value) {
|
||||
$output .= "$requirement_type: $value. ";
|
||||
}
|
||||
}
|
||||
return trim($output);
|
||||
}
|
||||
|
||||
}
|
25
core/modules/migrate/src/MigrateBuildDependencyInterface.php
Normal file
25
core/modules/migrate/src/MigrateBuildDependencyInterface.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateBuildDependencyInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
|
||||
interface MigrateBuildDependencyInterface {
|
||||
|
||||
/**
|
||||
* Builds a dependency tree for the migrations and set their order.
|
||||
*
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface[] $migrations
|
||||
* Array of loaded migrations with their declared dependencies.
|
||||
* @param array $dynamic_ids
|
||||
* Keys are dynamic ids (for example node:*) values are a list of loaded
|
||||
* migration ids (for example node:page, node:article).
|
||||
*
|
||||
* @return array
|
||||
* An array of migrations.
|
||||
*/
|
||||
public function buildDependencyMigration(array $migrations, array $dynamic_ids);
|
||||
}
|
77
core/modules/migrate/src/MigrateException.php
Normal file
77
core/modules/migrate/src/MigrateException.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateException.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
|
||||
/**
|
||||
* Defines the migrate exception class.
|
||||
*/
|
||||
class MigrateException extends \Exception {
|
||||
|
||||
/**
|
||||
* The level of the error being reported.
|
||||
*
|
||||
* The value is a Migration::MESSAGE_* constant.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $level;
|
||||
|
||||
/**
|
||||
* The status to record in the map table for the current item.
|
||||
*
|
||||
* The value is a MigrateMap::STATUS_* constant.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateException object.
|
||||
*
|
||||
* @param string $message
|
||||
* The message for the exception.
|
||||
* @param int $code
|
||||
* The Exception code.
|
||||
* @param \Exception $previous
|
||||
* The previous exception used for the exception chaining.
|
||||
* @param int $level
|
||||
* The level of the error, a Migration::MESSAGE_* constant.
|
||||
* @param int $status
|
||||
* The status of the item for the map table, a MigrateMap::STATUS_*
|
||||
* constant.
|
||||
*/
|
||||
public function __construct($message = NULL, $code = 0, \Exception $previous = NULL, $level = MigrationInterface::MESSAGE_ERROR, $status = MigrateIdMapInterface::STATUS_FAILED) {
|
||||
$this->level = $level;
|
||||
$this->status = $status;
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the level.
|
||||
*
|
||||
* @return int
|
||||
* An integer status code. @see Migration::MESSAGE_*
|
||||
*/
|
||||
public function getLevel() {
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of the current item.
|
||||
*
|
||||
* @return int
|
||||
* An integer status code. @see MigrateMap::STATUS_*
|
||||
*/
|
||||
public function getStatus() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
}
|
627
core/modules/migrate/src/MigrateExecutable.php
Normal file
627
core/modules/migrate/src/MigrateExecutable.php
Normal file
|
@ -0,0 +1,627 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateExecutable.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
|
||||
/**
|
||||
* Defines a migrate executable class.
|
||||
*/
|
||||
class MigrateExecutable implements MigrateExecutableInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The configuration of the migration to do.
|
||||
*
|
||||
* @var \Drupal\migrate\Entity\Migration
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The number of successfully imported rows since feedback was given.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $successesSinceFeedback;
|
||||
|
||||
/**
|
||||
* The number of rows that were successfully processed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $totalSuccesses;
|
||||
|
||||
/**
|
||||
* Status of one row.
|
||||
*
|
||||
* The value is a MigrateIdMapInterface::STATUS_* constant, for example:
|
||||
* STATUS_IMPORTED.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $sourceRowStatus;
|
||||
|
||||
/**
|
||||
* The number of rows processed.
|
||||
*
|
||||
* The total attempted, whether or not they were successful.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $totalProcessed;
|
||||
|
||||
/**
|
||||
* The queued messages not yet saved.
|
||||
*
|
||||
* Each element in the array is an array with two keys:
|
||||
* - 'message': The message string.
|
||||
* - 'level': The level, a MigrationInterface::MESSAGE_* constant.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queuedMessages = array();
|
||||
|
||||
/**
|
||||
* The options that can be set when executing the migration.
|
||||
*
|
||||
* Values can be set for:
|
||||
* - 'limit': Sets a time limit.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The PHP max_execution_time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxExecTime;
|
||||
|
||||
/**
|
||||
* The ratio of the memory limit at which an operation will be interrupted.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $memoryThreshold = 0.85;
|
||||
|
||||
/**
|
||||
* The ratio of the time limit at which an operation will be interrupted.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
public $timeThreshold = 0.90;
|
||||
|
||||
/**
|
||||
* The time limit when executing the migration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $limit = array();
|
||||
|
||||
/**
|
||||
* The configuration values of the source.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIdValues;
|
||||
|
||||
/**
|
||||
* The number of rows processed since feedback was given.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $processedSinceFeedback = 0;
|
||||
|
||||
/**
|
||||
* The PHP memory_limit expressed in bytes.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $memoryLimit;
|
||||
|
||||
/**
|
||||
* The rollback action to be saved for the current row.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $rollbackAction;
|
||||
|
||||
/**
|
||||
* An array of counts. Initially used for cache hit/miss tracking.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $counts = array();
|
||||
|
||||
/**
|
||||
* The maximum number of items to pass in a single call during a rollback.
|
||||
*
|
||||
* For use in bulkRollback(). Can be overridden in derived class constructor.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rollbackBatchSize = 50;
|
||||
|
||||
/**
|
||||
* The object currently being constructed.
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
protected $destinationValues;
|
||||
|
||||
/**
|
||||
* The source.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* The current data row retrieved from the source.
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
protected $sourceValues;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateExecutable and verifies and sets the memory limit.
|
||||
*
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration to run.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The message to record.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message) {
|
||||
$this->migration = $migration;
|
||||
$this->message = $message;
|
||||
$this->migration->getIdMap()->setMessage($message);
|
||||
// Record the memory limit in bytes
|
||||
$limit = trim(ini_get('memory_limit'));
|
||||
if ($limit == '-1') {
|
||||
$this->memoryLimit = PHP_INT_MAX;
|
||||
}
|
||||
else {
|
||||
if (!is_numeric($limit)) {
|
||||
$last = strtolower(substr($limit, -1));
|
||||
switch ($last) {
|
||||
case 'g':
|
||||
$limit *= 1024;
|
||||
case 'm':
|
||||
$limit *= 1024;
|
||||
case 'k':
|
||||
$limit *= 1024;
|
||||
break;
|
||||
default:
|
||||
throw new MigrateException($this->t('Invalid PHP memory_limit !limit',
|
||||
array('!limit' => $limit)));
|
||||
}
|
||||
}
|
||||
$this->memoryLimit = $limit;
|
||||
}
|
||||
// Record the maximum execution time limit.
|
||||
$this->maxExecTime = ini_get('max_execution_time');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source.
|
||||
*
|
||||
* Makes sure source is initialized based on migration settings.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* The source.
|
||||
*/
|
||||
protected function getSource() {
|
||||
if (!isset($this->source)) {
|
||||
$this->source = $this->migration->getSourcePlugin();
|
||||
|
||||
// @TODO, find out how to remove this.
|
||||
// @see https://www.drupal.org/node/2443617
|
||||
$this->source->migrateExecutable = $this;
|
||||
}
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import() {
|
||||
// Knock off migration if the requirements haven't been met.
|
||||
try {
|
||||
$this->migration->checkRequirements();
|
||||
}
|
||||
catch (RequirementsException $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration @id did not meet the requirements. @message @requirements', array(
|
||||
'@id' => $this->migration->id(),
|
||||
'@message' => $e->getMessage(),
|
||||
'@requirements' => $e->getRequirementsString(),
|
||||
)), 'error');
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
$return = MigrationInterface::RESULT_COMPLETED;
|
||||
$source = $this->getSource();
|
||||
$id_map = $this->migration->getIdMap();
|
||||
|
||||
try {
|
||||
$source->rewind();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration failed with source plugin exception: !e', array('!e' => $e->getMessage())), 'error');
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
$destination = $this->migration->getDestinationPlugin();
|
||||
while ($source->valid()) {
|
||||
$row = $source->current();
|
||||
if ($this->sourceIdValues = $row->getSourceIdValues()) {
|
||||
// Wipe old messages, and save any new messages.
|
||||
$id_map->delete($this->sourceIdValues, TRUE);
|
||||
$this->saveQueuedMessages();
|
||||
}
|
||||
|
||||
try {
|
||||
$this->processRow($row);
|
||||
$save = TRUE;
|
||||
}
|
||||
catch (MigrateSkipRowException $e) {
|
||||
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED, $this->rollbackAction);
|
||||
$save = FALSE;
|
||||
}
|
||||
|
||||
if ($save) {
|
||||
try {
|
||||
$destination_id_values = $destination->import($row, $id_map->lookupDestinationId($this->sourceIdValues));
|
||||
if ($destination_id_values) {
|
||||
// We do not save an idMap entry for config.
|
||||
if ($destination_id_values !== TRUE) {
|
||||
$id_map->saveIdMapping($row, $destination_id_values, $this->sourceRowStatus, $this->rollbackAction);
|
||||
}
|
||||
$this->successesSinceFeedback++;
|
||||
$this->totalSuccesses++;
|
||||
}
|
||||
else {
|
||||
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
|
||||
if (!$id_map->messageCount()) {
|
||||
$message = $this->t('New object was not saved, no error provided');
|
||||
$this->saveMessage($message);
|
||||
$this->message->display($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->migration->getIdMap()->saveIdMapping($row, array(), $e->getStatus(), $this->rollbackAction);
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
$this->message->display($e->getMessage(), 'error');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->migration->getIdMap()->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
|
||||
$this->handleException($e);
|
||||
}
|
||||
}
|
||||
$this->totalProcessed++;
|
||||
$this->processedSinceFeedback++;
|
||||
if ($high_water_property = $this->migration->get('highWaterProperty')) {
|
||||
$this->migration->saveHighWater($row->getSourceProperty($high_water_property['name']));
|
||||
}
|
||||
|
||||
// Reset row properties.
|
||||
unset($sourceValues, $destinationValues);
|
||||
$this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
|
||||
if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
|
||||
break;
|
||||
}
|
||||
if ($this->timeOptionExceeded()) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
$source->next();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration failed with source plugin exception: !e',
|
||||
array('!e' => $e->getMessage())), 'error');
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO uncomment this
|
||||
*/
|
||||
#$this->progressMessage($return);
|
||||
|
||||
$this->migration->setMigrationResult($return);
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processRow(Row $row, array $process = NULL, $value = NULL) {
|
||||
foreach ($this->migration->getProcessPlugins($process) as $destination => $plugins) {
|
||||
$multiple = FALSE;
|
||||
/** @var $plugin \Drupal\migrate\Plugin\MigrateProcessInterface */
|
||||
foreach ($plugins as $plugin) {
|
||||
$definition = $plugin->getPluginDefinition();
|
||||
// Many plugins expect a scalar value but the current value of the
|
||||
// pipeline might be multiple scalars (this is set by the previous
|
||||
// plugin) and in this case the current value needs to be iterated
|
||||
// and each scalar separately transformed.
|
||||
if ($multiple && !$definition['handle_multiples']) {
|
||||
$new_value = array();
|
||||
if (!is_array($value)) {
|
||||
throw new MigrateException(sprintf('Pipeline failed for destination %s: %s got instead of an array,', $destination, $value));
|
||||
}
|
||||
$break = FALSE;
|
||||
foreach ($value as $scalar_value) {
|
||||
try {
|
||||
$new_value[] = $plugin->transform($scalar_value, $this, $row, $destination);
|
||||
}
|
||||
catch (MigrateSkipProcessException $e) {
|
||||
$break = TRUE;
|
||||
}
|
||||
}
|
||||
$value = $new_value;
|
||||
if ($break) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$value = $plugin->transform($value, $this, $row, $destination);
|
||||
}
|
||||
catch (MigrateSkipProcessException $e) {
|
||||
break;
|
||||
}
|
||||
$multiple = $multiple || $plugin->multiple();
|
||||
}
|
||||
}
|
||||
// No plugins means do not set.
|
||||
if ($plugins) {
|
||||
$row->setDestinationProperty($destination, $value);
|
||||
}
|
||||
// Reset the value.
|
||||
$value = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the key array for the current source record.
|
||||
*
|
||||
* @return array
|
||||
* The current source IDs.
|
||||
*/
|
||||
protected function currentSourceIds() {
|
||||
return $this->getSource()->getCurrentIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether we've exceeded the designated time limit.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the threshold is exceeded, FALSE if not.
|
||||
*/
|
||||
protected function timeOptionExceeded() {
|
||||
// If there is no time limit, then it is not exceeded.
|
||||
if (!$time_limit = $this->getTimeLimit()) {
|
||||
return FALSE;
|
||||
}
|
||||
// Calculate if the time limit is exceeded.
|
||||
$time_elapsed = $this->getTimeElapsed();
|
||||
if ($time_elapsed >= $time_limit) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTimeLimit() {
|
||||
$limit = $this->limit;
|
||||
if (isset($limit['unit']) && isset($limit['value']) && ($limit['unit'] == 'seconds' || $limit['unit'] == 'second')) {
|
||||
return $limit['value'];
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveMessage($message, $level = MigrationInterface::MESSAGE_ERROR) {
|
||||
$this->migration->getIdMap()->saveMessage($this->sourceIdValues, $message, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queueMessage($message, $level = MigrationInterface::MESSAGE_ERROR) {
|
||||
$this->queuedMessages[] = array('message' => $message, 'level' => $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveQueuedMessages() {
|
||||
foreach ($this->queuedMessages as $queued_message) {
|
||||
$this->saveMessage($queued_message['message'], $queued_message['level']);
|
||||
}
|
||||
$this->queuedMessages = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for exceptional conditions, and display feedback.
|
||||
*
|
||||
* Standard top-of-loop stuff, common between rollback and import.
|
||||
*/
|
||||
protected function checkStatus() {
|
||||
if ($this->memoryExceeded()) {
|
||||
return MigrationInterface::RESULT_INCOMPLETE;
|
||||
}
|
||||
if ($this->maxExecTimeExceeded()) {
|
||||
return MigrationInterface::RESULT_INCOMPLETE;
|
||||
}
|
||||
/*
|
||||
* @TODO uncomment this
|
||||
if ($this->getStatus() == MigrationInterface::STATUS_STOPPING) {
|
||||
return MigrationBase::RESULT_STOPPED;
|
||||
}
|
||||
*/
|
||||
// If feedback is requested, produce a progress message at the proper time
|
||||
/*
|
||||
* @TODO uncomment this
|
||||
if (isset($this->feedback)) {
|
||||
if (($this->feedback_unit == 'seconds' && time() - $this->lastfeedback >= $this->feedback) ||
|
||||
($this->feedback_unit == 'items' && $this->processed_since_feedback >= $this->feedback)) {
|
||||
$this->progressMessage(MigrationInterface::RESULT_INCOMPLETE);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return MigrationInterface::RESULT_COMPLETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether we've exceeded the desired memory threshold.
|
||||
*
|
||||
* If so, output a message.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the threshold is exceeded, otherwise FALSE.
|
||||
*/
|
||||
protected function memoryExceeded() {
|
||||
$usage = $this->getMemoryUsage();
|
||||
$pct_memory = $usage / $this->memoryLimit;
|
||||
if (!$threshold = $this->memoryThreshold) {
|
||||
return FALSE;
|
||||
}
|
||||
if ($pct_memory > $threshold) {
|
||||
$this->message->display(
|
||||
$this->t('Memory usage is !usage (!pct% of limit !limit), reclaiming memory.',
|
||||
array('!pct' => round($pct_memory*100),
|
||||
'!usage' => $this->formatSize($usage),
|
||||
'!limit' => $this->formatSize($this->memoryLimit))),
|
||||
'warning');
|
||||
$usage = $this->attemptMemoryReclaim();
|
||||
$pct_memory = $usage / $this->memoryLimit;
|
||||
// Use a lower threshold - we don't want to be in a situation where we keep
|
||||
// coming back here and trimming a tiny amount
|
||||
if ($pct_memory > (0.90 * $threshold)) {
|
||||
$this->message->display(
|
||||
$this->t('Memory usage is now !usage (!pct% of limit !limit), not enough reclaimed, starting new batch',
|
||||
array('!pct' => round($pct_memory*100),
|
||||
'!usage' => $this->formatSize($usage),
|
||||
'!limit' => $this->formatSize($this->memoryLimit))),
|
||||
'warning');
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
$this->message->display(
|
||||
$this->t('Memory usage is now !usage (!pct% of limit !limit), reclaimed enough, continuing',
|
||||
array('!pct' => round($pct_memory*100),
|
||||
'!usage' => $this->formatSize($usage),
|
||||
'!limit' => $this->formatSize($this->memoryLimit))),
|
||||
'warning');
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memory usage so far.
|
||||
*
|
||||
* @return int
|
||||
* The memory usage.
|
||||
*/
|
||||
protected function getMemoryUsage() {
|
||||
return memory_get_usage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to reclaim memory.
|
||||
*
|
||||
* @return int
|
||||
* The memory usage after reclaim.
|
||||
*/
|
||||
protected function attemptMemoryReclaim() {
|
||||
// First, try resetting Drupal's static storage - this frequently releases
|
||||
// plenty of memory to continue.
|
||||
drupal_static_reset();
|
||||
// @TODO: explore resetting the container.
|
||||
return memory_get_usage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation for the given byte count.
|
||||
*
|
||||
* @param int $size
|
||||
* A size in bytes.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the size.
|
||||
*/
|
||||
protected function formatSize($size) {
|
||||
return format_size($size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether we're approaching the PHP maximum execution time limit.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the threshold is exceeded, FALSE if not.
|
||||
*/
|
||||
protected function maxExecTimeExceeded() {
|
||||
return $this->maxExecTime && (($this->getTimeElapsed() / $this->maxExecTime) > $this->timeThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed.
|
||||
*
|
||||
* This allows a test to set a fake elapsed time.
|
||||
*/
|
||||
protected function getTimeElapsed() {
|
||||
return time() - REQUEST_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an Exception object and both saves and displays it.
|
||||
*
|
||||
* Pulls in additional information on the location triggering the exception.
|
||||
*
|
||||
* @param \Exception $exception
|
||||
* Object representing the exception.
|
||||
* @param bool $save
|
||||
* (optional) Whether to save the message in the migration's mapping table.
|
||||
* Set to FALSE in contexts where this doesn't make sense.
|
||||
*/
|
||||
protected function handleException(\Exception $exception, $save = TRUE) {
|
||||
$result = Error::decodeException($exception);
|
||||
$message = $result['!message'] . ' (' . $result['%file'] . ':' . $result['%line'] . ')';
|
||||
if ($save) {
|
||||
$this->saveMessage($message);
|
||||
}
|
||||
$this->message->display($message, 'error');
|
||||
}
|
||||
|
||||
}
|
71
core/modules/migrate/src/MigrateExecutableInterface.php
Normal file
71
core/modules/migrate/src/MigrateExecutableInterface.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateExecutableInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
|
||||
interface MigrateExecutableInterface {
|
||||
|
||||
/**
|
||||
* Performs an import operation - migrate items from source to destination.
|
||||
*/
|
||||
public function import();
|
||||
|
||||
/**
|
||||
* Processes a row.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The $row to be processed.
|
||||
* @param array $process
|
||||
* (optional) A process pipeline configuration. If not set, the top level
|
||||
* process configuration in the migration entity is used.
|
||||
* @param mixed $value
|
||||
* (optional) Initial value of the pipeline for the first destination.
|
||||
* Usually setting this is not necessary as $process typically starts with
|
||||
* a 'get'. This is useful only when the $process contains a single
|
||||
* destination and needs to access a value outside of the source. See
|
||||
* \Drupal\migrate\Plugin\migrate\process\Iterator::transformKey for an
|
||||
* example.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function processRow(Row $row, array $process = NULL, $value = NULL);
|
||||
|
||||
/**
|
||||
* Returns the time limit.
|
||||
*
|
||||
* @return null|int
|
||||
* The time limit, NULL if no limit or if the units were not in seconds.
|
||||
*/
|
||||
public function getTimeLimit();
|
||||
|
||||
/**
|
||||
* Passes messages through to the map class.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to record.
|
||||
* @param int $level
|
||||
* (optional) Message severity (defaults to MESSAGE_ERROR).
|
||||
*/
|
||||
public function saveMessage($message, $level = MigrationInterface::MESSAGE_ERROR);
|
||||
|
||||
/**
|
||||
* Queues messages to be later saved through the map class.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to record.
|
||||
* @param int $level
|
||||
* (optional) Message severity (defaults to MESSAGE_ERROR).
|
||||
*/
|
||||
public function queueMessage($message, $level = MigrationInterface::MESSAGE_ERROR);
|
||||
|
||||
/**
|
||||
* Saves any messages we've queued up to the message table.
|
||||
*/
|
||||
public function saveQueuedMessages();
|
||||
}
|
35
core/modules/migrate/src/MigrateMessage.php
Normal file
35
core/modules/migrate/src/MigrateMessage.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateMessage.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Core\Logger\RfcLogLevel;
|
||||
|
||||
/**
|
||||
* Defines a migrate message class.
|
||||
*/
|
||||
class MigrateMessage implements MigrateMessageInterface {
|
||||
|
||||
/**
|
||||
* The map between migrate status and watchdog severity.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $map = array(
|
||||
'status' => RfcLogLevel::INFO,
|
||||
'error' => RfcLogLevel::ERROR,
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function display($message, $type = 'status') {
|
||||
$type = isset($this->map[$type]) ? $this->map[$type] : RfcLogLevel::NOTICE;
|
||||
\Drupal::logger('migrate')->log($type, $message);
|
||||
}
|
||||
|
||||
}
|
21
core/modules/migrate/src/MigrateMessageInterface.php
Normal file
21
core/modules/migrate/src/MigrateMessageInterface.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateMessageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
|
||||
interface MigrateMessageInterface {
|
||||
|
||||
/**
|
||||
* Displays a migrate message.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to display.
|
||||
* @param string $type
|
||||
* The type of message, for example: status or warning.
|
||||
*/
|
||||
public function display($message, $type = 'status');
|
||||
}
|
84
core/modules/migrate/src/MigratePassword.php
Normal file
84
core/modules/migrate/src/MigratePassword.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigratePassword.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Core\Password\PasswordInterface;
|
||||
|
||||
/**
|
||||
* Replaces the original 'password' service in order to prefix the MD5 re-hashed
|
||||
* passwords with the 'U' flag. The new salted hash is recreated on first login
|
||||
* similarly to the D6->D7 upgrade path.
|
||||
*/
|
||||
class MigratePassword implements PasswordInterface {
|
||||
|
||||
/**
|
||||
* The original password service.
|
||||
*
|
||||
* @var \Drupal\Core\Password\PasswordInterface
|
||||
*/
|
||||
protected $originalPassword;
|
||||
|
||||
/**
|
||||
* Indicates if MD5 password prefixing is enabled.
|
||||
*/
|
||||
protected $enabled = FALSE;
|
||||
|
||||
/**
|
||||
* Builds the replacement password service class.
|
||||
*
|
||||
* @param \Drupal\Core\Password\PasswordInterface $original_password
|
||||
* The password object.
|
||||
*/
|
||||
public function __construct(PasswordInterface $original_password) {
|
||||
$this->originalPassword = $original_password;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check($password, $hash) {
|
||||
return $this->originalPassword->check($password, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function needsRehash($hash) {
|
||||
return $this->originalPassword->needsRehash($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hash($password) {
|
||||
$hash = $this->originalPassword->hash($password);
|
||||
|
||||
// Allow prefixing only if the service was asked to prefix. Check also if
|
||||
// the $password pattern is conforming to a MD5 result.
|
||||
if ($this->enabled && preg_match('/^[0-9a-f]{32}$/', $password)) {
|
||||
$hash = 'U' . $hash;
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the MD5 password prefixing.
|
||||
*/
|
||||
public function enableMd5Prefixing() {
|
||||
$this->enabled = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the MD5 password prefixing.
|
||||
*/
|
||||
public function disableMd5Prefixing() {
|
||||
$this->enabled = FALSE;
|
||||
}
|
||||
|
||||
}
|
30
core/modules/migrate/src/MigrateServiceProvider.php
Normal file
30
core/modules/migrate/src/MigrateServiceProvider.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateServiceProvider.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Swaps the original 'password' service in order to handle password hashing for
|
||||
* user migrations that have passwords hashed to MD5.
|
||||
*
|
||||
* @see \Drupal\migrate\MigratePassword
|
||||
* @see \Drupal\Core\Password\PhpassHashedPassword
|
||||
*/
|
||||
class MigrateServiceProvider implements ServiceModifierInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alter(ContainerBuilder $container) {
|
||||
$container->setDefinition('password_original', $container->getDefinition('password'));
|
||||
$container->setDefinition('password', $container->getDefinition('password_migrate'));
|
||||
}
|
||||
|
||||
}
|
15
core/modules/migrate/src/MigrateSkipProcessException.php
Normal file
15
core/modules/migrate/src/MigrateSkipProcessException.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateSkipProcessException.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* This exception is thrown when the rest of the process should be skipped.
|
||||
*/
|
||||
class MigrateSkipProcessException extends \Exception {
|
||||
|
||||
}
|
15
core/modules/migrate/src/MigrateSkipRowException.php
Normal file
15
core/modules/migrate/src/MigrateSkipRowException.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrateSkipRowException.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* This exception is thrown when a row should be skipped.
|
||||
*/
|
||||
class MigrateSkipRowException extends \Exception {
|
||||
|
||||
}
|
93
core/modules/migrate/src/MigrationStorage.php
Normal file
93
core/modules/migrate/src/MigrationStorage.php
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\MigrationStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
|
||||
|
||||
/**
|
||||
* Storage for migration entities.
|
||||
*/
|
||||
class MigrationStorage extends ConfigEntityStorage implements MigrateBuildDependencyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildDependencyMigration(array $migrations, array $dynamic_ids) {
|
||||
// Migration dependencies defined in the migration storage can be
|
||||
// optional or required. If an optional dependency does not run, the current
|
||||
// migration is still OK to go. Both optional and required dependencies
|
||||
// (if run at all) must run before the current migration.
|
||||
$dependency_graph = array();
|
||||
$requirement_graph = array();
|
||||
$different = FALSE;
|
||||
foreach ($migrations as $migration) {
|
||||
/** @var \Drupal\migrate\Entity\MigrationInterface $migration */
|
||||
$id = $migration->id();
|
||||
$requirements[$id] = array();
|
||||
$dependency_graph[$id]['edges'] = array();
|
||||
$migration_dependencies = $migration->getMigrationDependencies();
|
||||
|
||||
if (isset($migration_dependencies['required'])) {
|
||||
foreach ($migration_dependencies['required'] as $dependency) {
|
||||
if (!isset($dynamic_ids[$dependency])) {
|
||||
$this->addDependency($requirement_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
$this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
}
|
||||
if (isset($migration_dependencies['optional'])) {
|
||||
foreach ($migration_dependencies['optional'] as $dependency) {
|
||||
$different = TRUE;
|
||||
$this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
$graph_object = new Graph($dependency_graph);
|
||||
$dependency_graph = $graph_object->searchAndSort();
|
||||
if ($different) {
|
||||
$graph_object = new Graph($requirement_graph);
|
||||
$requirement_graph = $graph_object->searchAndSort();
|
||||
}
|
||||
else {
|
||||
$requirement_graph = $dependency_graph;
|
||||
}
|
||||
$weights = array();
|
||||
foreach ($migrations as $migration_id => $migration) {
|
||||
// Populate a weights array to use with array_multisort later.
|
||||
$weights[] = $dependency_graph[$migration_id]['weight'];
|
||||
if (!empty($requirement_graph[$migration_id]['paths'])) {
|
||||
$migration->set('requirements', $requirement_graph[$migration_id]['paths']);
|
||||
}
|
||||
}
|
||||
array_multisort($weights, SORT_DESC, SORT_NUMERIC, $migrations);
|
||||
|
||||
return $migrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more dependencies to a graph.
|
||||
*
|
||||
* @param array $graph
|
||||
* The graph so far.
|
||||
* @param int $id
|
||||
* The migration id.
|
||||
* @param string $dependency
|
||||
* The dependency string.
|
||||
* @param array $dynamic_ids
|
||||
* The dynamic id mapping.
|
||||
*/
|
||||
protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) {
|
||||
$dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : array($dependency);
|
||||
if (!isset($graph[$id]['edges'])) {
|
||||
$graph[$id]['edges'] = array();
|
||||
}
|
||||
$graph[$id]['edges'] += array_combine($dependencies, $dependencies);
|
||||
}
|
||||
|
||||
}
|
77
core/modules/migrate/src/Plugin/Derivative/MigrateEntity.php
Normal file
77
core/modules/migrate/src/Plugin/Derivative/MigrateEntity.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\Derivative\MigrateEntity.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\Derivative;
|
||||
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
class MigrateEntity implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = array();
|
||||
|
||||
/**
|
||||
* The entity definitions
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
*/
|
||||
protected $entityDefinitions;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateEntity object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_definitions
|
||||
* A list of entity definition objects.
|
||||
*/
|
||||
public function __construct(array $entity_definitions) {
|
||||
$this->entityDefinitions = $entity_definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity.manager')->getDefinitions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
|
||||
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
$this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
|
||||
$class = is_subclass_of($entity_info->getClass(), 'Drupal\Core\Config\Entity\ConfigEntityInterface') ?
|
||||
'Drupal\migrate\Plugin\migrate\destination\EntityConfigBase' :
|
||||
'Drupal\migrate\Plugin\migrate\destination\EntityContentBase';
|
||||
$this->derivatives[$entity_type] = array(
|
||||
'id' => "entity:$entity_type",
|
||||
'class' => $class,
|
||||
'requirements_met' => 1,
|
||||
'provider' => $entity_info->getProvider(),
|
||||
);
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\Derivative\MigrateEntityRevision.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\Derivative;
|
||||
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
class MigrateEntityRevision implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = array();
|
||||
|
||||
/**
|
||||
* The entity definitions
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
*/
|
||||
protected $entityDefinitions;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateEntity object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_definitions
|
||||
* A list of entity definition objects.
|
||||
*/
|
||||
public function __construct(array $entity_definitions) {
|
||||
$this->entityDefinitions = $entity_definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity.manager')->getDefinitions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
|
||||
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
$this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
|
||||
if ($entity_info->getKey('revision')) {
|
||||
$this->derivatives[$entity_type] = array(
|
||||
'id' => "entity_revision:$entity_type",
|
||||
'class' => 'Drupal\migrate\Plugin\migrate\destination\EntityRevision',
|
||||
'requirements_met' => 1,
|
||||
'provider' => $entity_info->getProvider(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
109
core/modules/migrate/src/Plugin/MigrateDestinationInterface.php
Normal file
109
core/modules/migrate/src/Plugin/MigrateDestinationInterface.php
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\MigrateDestinationInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
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\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateDestinationInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Get the destination ids.
|
||||
*
|
||||
* To support MigrateIdMap maps, derived destination classes should return
|
||||
* schema field definition(s) corresponding to the primary key of the
|
||||
* destination being implemented. These are used to construct the destination
|
||||
* key fields of the map table for a migration using this destination.
|
||||
*
|
||||
* @return array
|
||||
* An array of ids.
|
||||
*/
|
||||
public function getIds();
|
||||
|
||||
/**
|
||||
* Returns an array of destination fields.
|
||||
*
|
||||
* Derived classes must implement fields(), returning a list of available
|
||||
* destination fields.
|
||||
*
|
||||
* @todo Review the cases where we need the Migration parameter,
|
||||
* can we avoid that?
|
||||
*
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* (optional) the migration containing this destination.
|
||||
*
|
||||
* @return array
|
||||
* - Keys: machine names of the fields
|
||||
* - Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL);
|
||||
|
||||
|
||||
/**
|
||||
* Allows pre-processing of an import.
|
||||
*
|
||||
* Derived classes may implement preImport() to do any processing they need
|
||||
* done before over all source rows.
|
||||
*/
|
||||
public function preImport();
|
||||
|
||||
/**
|
||||
* Allows pre-processing of a rollback.
|
||||
*/
|
||||
public function preRollback();
|
||||
|
||||
/**
|
||||
* Allows post-processing of an import.
|
||||
*
|
||||
* Derived classes may implement postImport(), to do any processing they need
|
||||
* done after looping over all source rows.
|
||||
*/
|
||||
public function postImport();
|
||||
|
||||
/**
|
||||
* Allows post-processing of a rollback.
|
||||
*/
|
||||
public function postRollback();
|
||||
|
||||
/**
|
||||
* Import the row.
|
||||
*
|
||||
* Derived classes must implement import(), to construct one new object
|
||||
* (pre-populated) using ID mappings in the Migration).
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
* @param array $old_destination_id_values
|
||||
* The old destination ids.
|
||||
*
|
||||
* @return mixed
|
||||
* The entity id or an indication of success.
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array());
|
||||
|
||||
/**
|
||||
* Delete the specified IDs from the target Drupal.
|
||||
*
|
||||
* @param array $destination_identifiers
|
||||
* The destination ids to delete.
|
||||
*/
|
||||
public function rollbackMultiple(array $destination_identifiers);
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\MigrateDestinationPluginManager.
|
||||
*/
|
||||
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Plugin manager for migrate destination plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigrateDestinationPluginManager extends MigratePluginManager {
|
||||
|
||||
/**
|
||||
* The theme handler
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ThemeHandlerInterface
|
||||
*/
|
||||
protected $themeHandler;
|
||||
|
||||
/**
|
||||
* An associative array where the keys are the enabled modules and themes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $providers;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, $annotation = 'Drupal\migrate\Annotation\MigrateDestination') {
|
||||
parent::__construct($type, $namespaces, $cache_backend, $module_handler, $annotation);
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array(), MigrationInterface $migration = NULL) {
|
||||
if (substr($plugin_id, 0, 7) == 'entity:' && !$this->entityManager->getDefinition(substr($plugin_id, 7), FALSE)) {
|
||||
$plugin_id = 'null';
|
||||
}
|
||||
return parent::createInstance($plugin_id, $configuration, $migration);
|
||||
}
|
||||
|
||||
}
|
242
core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
Normal file
242
core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
Normal file
|
@ -0,0 +1,242 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\MigrateIdMapInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Defines an interface for migrate ID mappings.
|
||||
*
|
||||
* Migrate ID mappings maintain a relation between source ID and destination ID
|
||||
* for audit and rollback purposes.
|
||||
*/
|
||||
interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Codes reflecting the current status of a map row.
|
||||
*/
|
||||
const STATUS_IMPORTED = 0;
|
||||
const STATUS_NEEDS_UPDATE = 1;
|
||||
const STATUS_IGNORED = 2;
|
||||
const STATUS_FAILED = 3;
|
||||
|
||||
/**
|
||||
* Codes reflecting how to handle the destination item on rollback.
|
||||
*/
|
||||
const ROLLBACK_DELETE = 0;
|
||||
const ROLLBACK_PRESERVE = 1;
|
||||
|
||||
/**
|
||||
* Saves a mapping from the source identifiers to the destination identifiers.
|
||||
*
|
||||
* Called upon import of one row, we record a mapping from the source ID
|
||||
* to the destination ID. Also may be called, setting the third parameter to
|
||||
* NEEDS_UPDATE, to signal an existing record should be re-migrated.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The raw source data. We use the ID map derived from the source object
|
||||
* to get the source identifier values.
|
||||
* @param array $destination_id_values
|
||||
* An array of destination identifier values.
|
||||
* @param int $status
|
||||
* Status of the source row in the map.
|
||||
* @param int $rollback_action
|
||||
* How to handle the destination object on rollback.
|
||||
*/
|
||||
public function saveIdMapping(Row $row, array $destination_id_values, $status = self::STATUS_IMPORTED, $rollback_action = self::ROLLBACK_DELETE);
|
||||
|
||||
/**
|
||||
* Saves a message related to a source record in the migration message table.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier values of the record in error.
|
||||
* @param string $message
|
||||
* The message to record.
|
||||
* @param int $level
|
||||
* Optional message severity (defaults to MESSAGE_ERROR).
|
||||
*/
|
||||
public function saveMessage(array $source_id_values, $message, $level = MigrationInterface::MESSAGE_ERROR);
|
||||
|
||||
/**
|
||||
* Prepares to run a full update.
|
||||
*
|
||||
* Prepares this migration to run as an update - that is, in addition to
|
||||
* unmigrated content (source records not in the map table) being imported,
|
||||
* previously-migrated content will also be updated in place by marking all
|
||||
* previously-imported content as ready to be re-imported.
|
||||
*/
|
||||
public function prepareUpdate();
|
||||
|
||||
/**
|
||||
* Returns the number of processed items in the map.
|
||||
*
|
||||
* @return int
|
||||
* The count of records in the map table.
|
||||
*/
|
||||
public function processedCount();
|
||||
|
||||
/**
|
||||
* Returns the number of imported items in the map.
|
||||
*
|
||||
* @return int
|
||||
* The number of imported items.
|
||||
*/
|
||||
public function importedCount();
|
||||
|
||||
|
||||
/**
|
||||
* Returns a count of items which are marked as needing update.
|
||||
*
|
||||
* @return int
|
||||
* The number of items which need updating.
|
||||
*/
|
||||
public function updateCount();
|
||||
|
||||
/**
|
||||
* Returns the number of items that failed to import.
|
||||
*
|
||||
* @return int
|
||||
* The number of items that errored out.
|
||||
*/
|
||||
public function errorCount();
|
||||
|
||||
/**
|
||||
* Returns the number of messages saved.
|
||||
*
|
||||
* @return int
|
||||
* The number of messages.
|
||||
*/
|
||||
public function messageCount();
|
||||
|
||||
/**
|
||||
* Deletes the map and message entries for a given source record.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier values of the record to delete.
|
||||
* @param bool $messages_only
|
||||
* TRUE to only delete the migrate messages.
|
||||
*/
|
||||
public function delete(array $source_id_values, $messages_only = FALSE);
|
||||
|
||||
/**
|
||||
* Deletes the map and message table entries for a given destination row.
|
||||
*
|
||||
* @param array $destination_id_values
|
||||
* The destination identifier values we should do the deletes for.
|
||||
*/
|
||||
public function deleteDestination(array $destination_id_values);
|
||||
|
||||
/**
|
||||
* Deletes the map and message entries for a set of given source records.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The identifier values of the sources we should do the deletes for. Each
|
||||
* array member is an array of identifier values for one source row.
|
||||
*/
|
||||
public function deleteBulk(array $source_id_values);
|
||||
|
||||
/**
|
||||
* Clears all messages from the map.
|
||||
*/
|
||||
public function clearMessages();
|
||||
|
||||
/**
|
||||
* Retrieves a row from the map table based on source identifier values.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier values of the record to retrieve.
|
||||
*
|
||||
* @return array
|
||||
* The raw row data as an associative array.
|
||||
*/
|
||||
public function getRowBySource(array $source_id_values);
|
||||
|
||||
/**
|
||||
* Retrieves a row by the destination identifiers.
|
||||
*
|
||||
* @param array $destination_id_values
|
||||
* The destination identifier values of the record to retrieve.
|
||||
*
|
||||
* @return array
|
||||
* The row(s) of data.
|
||||
*/
|
||||
public function getRowByDestination(array $destination_id_values);
|
||||
|
||||
/**
|
||||
* Retrieves an array of map rows marked as needing update.
|
||||
*
|
||||
* @param int $count
|
||||
* The maximum number of rows to return.
|
||||
*
|
||||
* @return array
|
||||
* Array of map row objects that need updating.
|
||||
*/
|
||||
public function getRowsNeedingUpdate($count);
|
||||
|
||||
/**
|
||||
* Looks up the source identifier.
|
||||
*
|
||||
* Given a (possibly multi-field) destination identifier value, return the
|
||||
* (possibly multi-field) source identifier value mapped to it.
|
||||
*
|
||||
* @param array $destination_id_values
|
||||
* The destination identifier values of the record.
|
||||
*
|
||||
* @return array
|
||||
* The source identifier values of the record, or NULL on failure.
|
||||
*/
|
||||
public function lookupSourceID(array $destination_id_values);
|
||||
|
||||
/**
|
||||
* Looks up the destination identifier.
|
||||
*
|
||||
* Given a (possibly multi-field) source identifier value, return the
|
||||
* (possibly multi-field) destination identifier value it is mapped to.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier values of the record.
|
||||
*
|
||||
* @return array
|
||||
* The destination identifier values of the record, or NULL on failure.
|
||||
*/
|
||||
public function lookupDestinationId(array $source_id_values);
|
||||
|
||||
/**
|
||||
* Removes any persistent storage used by this map.
|
||||
*
|
||||
* For example, remove the map and message tables.
|
||||
*/
|
||||
public function destroy();
|
||||
|
||||
/**
|
||||
* Gets the qualified map table.
|
||||
*
|
||||
* @todo Remove this as this is SQL only and so doesn't belong to the interface.
|
||||
*/
|
||||
public function getQualifiedMapTableName();
|
||||
|
||||
/**
|
||||
* Sets the migrate message.
|
||||
*
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The message to display.
|
||||
*/
|
||||
public function setMessage(MigrateMessageInterface $message);
|
||||
|
||||
/**
|
||||
* Sets a specified record to be updated, if it exists.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier values of the record.
|
||||
*/
|
||||
public function setUpdate(array $source_id_values);
|
||||
|
||||
}
|
89
core/modules/migrate/src/Plugin/MigratePluginManager.php
Normal file
89
core/modules/migrate/src/Plugin/MigratePluginManager.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\MigratePluginManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Manages migrate plugins.
|
||||
*
|
||||
* @see hook_migrate_info_alter()
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\process\ProcessPluginBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigratePluginManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a MigratePluginManager object.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of the plugin: row, source, process, destination, entity_field,
|
||||
* id_map.
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
* @param string $annotation
|
||||
* The annotation class name.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation = 'Drupal\Component\Annotation\PluginID') {
|
||||
$plugin_interface = isset($plugin_interface_map[$type]) ? $plugin_interface_map[$type] : NULL;
|
||||
parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, $plugin_interface, $annotation);
|
||||
$this->alterInfo('migrate_' . $type . '_info');
|
||||
$this->setCacheBackend($cache_backend, 'migrate_plugins_' . $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array(), MigrationInterface $migration = NULL) {
|
||||
$plugin_definition = $this->getDefinition($plugin_id);
|
||||
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition);
|
||||
// If the plugin provides a factory method, pass the container to it.
|
||||
if (is_subclass_of($plugin_class, 'Drupal\Core\Plugin\ContainerFactoryPluginInterface')) {
|
||||
$plugin = $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition, $migration);
|
||||
}
|
||||
else {
|
||||
$plugin = new $plugin_class($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
}
|
||||
return $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for the plugin type to interface map.
|
||||
*
|
||||
* @return array
|
||||
* An array map from plugin type to interface.
|
||||
*/
|
||||
protected function getPluginInterfaceMap() {
|
||||
return [
|
||||
'destination' => 'Drupal\migrate\Plugin\MigrateDestinationInterface',
|
||||
'process' => 'Drupal\migrate\Plugin\MigrateProcessInterface',
|
||||
'source' => 'Drupal\migrate\Plugin\MigrateSourceInterface',
|
||||
'id_map' => 'Drupal\migrate\Plugin\MigrateIdMapInterface',
|
||||
'entity_field' => 'Drupal\migrate\Plugin\MigrateEntityDestinationFieldInterface',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
62
core/modules/migrate/src/Plugin/MigrateProcessInterface.php
Normal file
62
core/modules/migrate/src/Plugin/MigrateProcessInterface.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\MigrateProcessInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* An interface for migrate process plugins.
|
||||
*
|
||||
* A process plugin 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.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\ProcessPluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateProcessInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Performs the associated process.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The value to be transformed.
|
||||
* @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
|
||||
* The migration in which this process is being executed.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row from the source to process. Normally, just transforming the
|
||||
* value is adequate but very rarely you might need to change two columns
|
||||
* at the same time or something like that.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used
|
||||
* together with the $row above.
|
||||
*
|
||||
* @return string|array
|
||||
* The newly transformed value.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property);
|
||||
|
||||
/**
|
||||
* Indicates whether the returned value requires multiple handling.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE when the returned value contains a list of values to be processed.
|
||||
* For example, when the 'source' property is a string and the value found
|
||||
* is an array.
|
||||
*/
|
||||
public function multiple();
|
||||
|
||||
}
|
65
core/modules/migrate/src/Plugin/MigrateSourceInterface.php
Normal file
65
core/modules/migrate/src/Plugin/MigrateSourceInterface.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\MigrateSourceInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Defines an interface for migrate sources.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Returns available fields on the source.
|
||||
*
|
||||
* @return array
|
||||
* Available fields in the source, keys are the field machine names as used
|
||||
* in field mappings, values are descriptions.
|
||||
*/
|
||||
public function fields();
|
||||
|
||||
/**
|
||||
* Returns the iterator that will yield the row arrays to be processed.
|
||||
*
|
||||
* @return \Iterator
|
||||
* The iterator object.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Cannot obtain a valid iterator.
|
||||
*/
|
||||
public function getIterator();
|
||||
|
||||
/**
|
||||
* Add additional data to the row.
|
||||
*
|
||||
* @param \Drupal\Migrate\Row $row
|
||||
* The row object.
|
||||
*
|
||||
* @return bool
|
||||
* FALSE if this row needs to be skipped.
|
||||
*/
|
||||
public function prepareRow(Row $row);
|
||||
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* Get the source ids.
|
||||
*
|
||||
* @return array
|
||||
* The source ids.
|
||||
*/
|
||||
public function getIds();
|
||||
|
||||
}
|
23
core/modules/migrate/src/Plugin/RequirementsInterface.php
Normal file
23
core/modules/migrate/src/Plugin/RequirementsInterface.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\RequirementsInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
/**
|
||||
* An interface to check for a migrate plugin requirements.
|
||||
*/
|
||||
interface RequirementsInterface {
|
||||
|
||||
/**
|
||||
* Checks if requirements for this plugin are OK.
|
||||
*
|
||||
* @throws \Drupal\migrate\Exception\RequirementsException
|
||||
* Thrown when requirements are not met.
|
||||
*/
|
||||
public function checkRequirements();
|
||||
|
||||
}
|
32
core/modules/migrate/src/Plugin/SourceEntityInterface.php
Normal file
32
core/modules/migrate/src/Plugin/SourceEntityInterface.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\SourceEntityInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
/**
|
||||
* Interface for sources providing an entity.
|
||||
*/
|
||||
interface SourceEntityInterface {
|
||||
|
||||
/**
|
||||
* Whether this migration has a bundle migration.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE when the bundle_migration key is required.
|
||||
*/
|
||||
public function bundleMigrationRequired();
|
||||
|
||||
/**
|
||||
* The entity type id (user, node etc).
|
||||
*
|
||||
* This function is used when bundleMigrationRequired() is FALSE.
|
||||
*
|
||||
* @return string
|
||||
* The entity type id.
|
||||
*/
|
||||
public function entityTypeId();
|
||||
|
||||
}
|
35
core/modules/migrate/src/Plugin/migrate/destination/Book.php
Normal file
35
core/modules/migrate/src/Plugin/migrate/destination/Book.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\Book.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "book",
|
||||
* provider = "book"
|
||||
* )
|
||||
*/
|
||||
class Book extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getEntityTypeId($plugin_id) {
|
||||
return 'node';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
$entity->book = $row->getDestinationProperty('book');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\ComponentEntityDisplayBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
abstract class ComponentEntityDisplayBase extends DestinationBase {
|
||||
|
||||
const MODE_NAME = '';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$values = array();
|
||||
// array_intersect_key() won't work because the order is important because
|
||||
// this is also the return value.
|
||||
foreach (array_keys($this->getIds()) as $id) {
|
||||
$values[$id] = $row->getDestinationProperty($id);
|
||||
}
|
||||
$entity = $this->getEntity($values['entity_type'], $values['bundle'], $values[static::MODE_NAME]);
|
||||
if (!$row->getDestinationProperty('hidden')) {
|
||||
$entity->setComponent($values['field_name'], $row->getDestinationProperty('options') ?: array());
|
||||
}
|
||||
else {
|
||||
$entity->removeComponent($values['field_name']);
|
||||
}
|
||||
$entity->save();
|
||||
return array_values($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['bundle']['type'] = 'string';
|
||||
$ids[static::MODE_NAME]['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
// This is intentionally left empty.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to retrieve.
|
||||
* @param string $bundle
|
||||
* The entity bundle.
|
||||
* @param string $mode
|
||||
* The display mode.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Display\EntityDisplayInterface
|
||||
* The entity display object.
|
||||
*/
|
||||
protected abstract function getEntity($entity_type, $bundle, $mode);
|
||||
|
||||
}
|
121
core/modules/migrate/src/Plugin/migrate/destination/Config.php
Normal file
121
core/modules/migrate/src/Plugin/migrate/destination/Config.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\Config.
|
||||
*
|
||||
* Provides Configuration Management destination plugin.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\DependencyTrait;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Config\Config as ConfigObject;
|
||||
|
||||
/**
|
||||
* Persist data to the config system.
|
||||
*
|
||||
* When a property is NULL, the default is used unless the configuration option
|
||||
* 'store null' is set to TRUE.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "config"
|
||||
* )
|
||||
*/
|
||||
class Config extends DestinationBase implements ContainerFactoryPluginInterface, DependentPluginInterface {
|
||||
use DependencyTrait;
|
||||
|
||||
/**
|
||||
* The config object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructs a Config destination object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration entity.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->config = $config_factory->getEditable($configuration['config_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('config.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
foreach ($row->getRawDestination() as $key => $value) {
|
||||
if (isset($value) || !empty($this->configuration['store null'])) {
|
||||
$this->config->set(str_replace(Row::PROPERTY_SEPARATOR, '.', $key), $value);
|
||||
}
|
||||
}
|
||||
$this->config->save();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception because config can not be rolled back.
|
||||
*
|
||||
* @param array $destination_keys
|
||||
* The array of destination ids to roll back.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function rollbackMultiple(array $destination_keys) {
|
||||
throw new MigrateException('Configuration can not be rolled back');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
// @todo Dynamically fetch fields using Config Schema API.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$provider = explode('.', $this->config->getName(), 2)[0];
|
||||
$this->addDependency('module', $provider);
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\DestinationBase.
|
||||
*/
|
||||
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrateDestinationInterface;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
|
||||
/**
|
||||
* Base class for migrate destination classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
abstract class DestinationBase extends PluginBase implements MigrateDestinationInterface, RequirementsInterface {
|
||||
|
||||
/**
|
||||
* The migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Entity\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Constructs an entity destination plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param MigrationInterface $migration
|
||||
* The migration.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequirements() {
|
||||
if (empty($this->pluginDefinition['requirements_met'])) {
|
||||
throw new RequirementsException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the Row before it is imported.
|
||||
*/
|
||||
public function preImport() {
|
||||
// By default we do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the Row before it is rolled back.
|
||||
*/
|
||||
public function preRollback() {
|
||||
// By default we do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postImport() {
|
||||
// By default we do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postRollback() {
|
||||
// By default we do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollbackMultiple(array $destination_identifiers) {
|
||||
// By default we do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCreated() {
|
||||
// TODO: Implement getCreated() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUpdated() {
|
||||
// TODO: Implement getUpdated() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetStats() {
|
||||
// TODO: Implement resetStats() method.
|
||||
}
|
||||
|
||||
}
|
182
core/modules/migrate/src/Plugin/migrate/destination/Entity.php
Normal file
182
core/modules/migrate/src/Plugin/migrate/destination/Entity.php
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\Entity.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Entity\DependencyTrait;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity",
|
||||
* deriver = "Drupal\migrate\Plugin\Derivative\MigrateEntity"
|
||||
* )
|
||||
*/
|
||||
abstract class Entity extends DestinationBase implements ContainerFactoryPluginInterface, DependentPluginInterface {
|
||||
use DependencyTrait;
|
||||
|
||||
/**
|
||||
* The entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The list of the bundles of this entity type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bundles;
|
||||
|
||||
/**
|
||||
* Construct a new entity.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param 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) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->storage = $storage;
|
||||
$this->bundles = $bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$entity_type_id = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity.manager')->getStorage($entity_type_id),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type_id))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the entity type from configuration or plugin id.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin id.
|
||||
*
|
||||
* @return string
|
||||
* The entity type.
|
||||
*/
|
||||
protected static function getEntityTypeId($plugin_id) {
|
||||
// Remove "entity:"
|
||||
return substr($plugin_id, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
// TODO: Implement fields() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or loads an entity.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
* @param array $old_destination_id_values
|
||||
* The old destination ids.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The entity we're importing into.
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
$entity_id = $old_destination_id_values ? reset($old_destination_id_values) : $this->getEntityId($row);
|
||||
if (!empty($entity_id) && ($entity = $this->storage->load($entity_id))) {
|
||||
$this->updateEntity($entity, $row);
|
||||
}
|
||||
else {
|
||||
$values = $row->getDestination();
|
||||
// Stubs might not have the bundle specified.
|
||||
if ($row->isStub()) {
|
||||
$values = $this->processStubValues($values);
|
||||
}
|
||||
$entity = $this->storage->create($values);
|
||||
$entity->enforceIsNew();
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity id of the row.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row of data.
|
||||
* @return string
|
||||
* The entity id for the row we're importing.
|
||||
*/
|
||||
protected function getEntityId(Row $row) {
|
||||
return $row->getDestinationProperty($this->getKey('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the stub values.
|
||||
*
|
||||
* @param array $values
|
||||
* An array of destination values.
|
||||
*
|
||||
* @return array
|
||||
* The processed stub values.
|
||||
*/
|
||||
protected function processStubValues(array $values) {
|
||||
$values = array_intersect_key($values, $this->getIds());
|
||||
|
||||
$bundle_key = $this->getKey('bundle');
|
||||
if ($bundle_key && !isset($values[$bundle_key])) {
|
||||
$values[$bundle_key] = reset($this->bundles);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific entity key.
|
||||
*
|
||||
* @param string $key
|
||||
* The name of the entity key to return.
|
||||
*
|
||||
* @return string|bool
|
||||
* The entity key, or FALSE if it does not exist.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityTypeInterface::getKeys()
|
||||
*/
|
||||
protected function getKey($key) {
|
||||
return $this->storage->getEntityType()->getKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$this->addDependency('module', $this->storage->getEntityType()->getProvider());
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityBaseFieldOverride.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:base_field_override"
|
||||
* )
|
||||
*/
|
||||
class EntityBaseFieldOverride extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntityId(Row $row) {
|
||||
$entity_type = $row->getDestinationProperty('entity_type');
|
||||
$bundle = $row->getDestinationProperty('bundle');
|
||||
$field_name = $row->getDestinationProperty('field_name');
|
||||
return "$entity_type.$bundle.$field_name";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityComment.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigratePluginManager;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:comment"
|
||||
* )
|
||||
*/
|
||||
class EntityComment extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* The state storage object.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Builds an comment entity destination.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager
|
||||
* The migrate plugin manager.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state storage object.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StateInterface $state) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$entity_type = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('state')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
if ($row->isStub() && ($state = $this->state->get('comment.maintain_entity_statistics', 0))) {
|
||||
$this->state->set('comment.maintain_entity_statistics', 0);
|
||||
}
|
||||
$return = parent::import($row, $old_destination_id_values);
|
||||
if ($row->isStub() && $state) {
|
||||
$this->state->set('comment.maintain_entity_statistics', $state);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityCommentType.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:comment_type"
|
||||
* )
|
||||
*/
|
||||
class EntityCommentType extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$entity_ids = parent::import($row, $old_destination_id_values);
|
||||
\Drupal::service('comment.manager')->addBodyField(reset($entity_ids));
|
||||
return $entity_ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityConfigBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Class for importing configuration entities.
|
||||
*
|
||||
* This class serves as the import class for most configuration entities.
|
||||
* It can be necessary to provide a specific entity class if the configuration
|
||||
* entity has a compound id (see EntityFieldEntity) or it has specific setter
|
||||
* methods (see EntityDateFormat). When implementing an entity destination for
|
||||
* the latter case, make sure to add a test not only for importing but also
|
||||
* for re-importing (if that is supported).
|
||||
*/
|
||||
class EntityConfigBase extends Entity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
if ($row->isStub()) {
|
||||
throw new MigrateException('Config entities can not be stubbed.');
|
||||
}
|
||||
$ids = $this->getIds();
|
||||
$id_key = $this->getKey('id');
|
||||
if (count($ids) > 1) {
|
||||
// Ids is keyed by the key name so grab the keys.
|
||||
$id_keys = array_keys($ids);
|
||||
if (!$row->getDestinationProperty($id_key)) {
|
||||
// Set the id into the destination in for form "val1.val2.val3".
|
||||
$row->setDestinationProperty($id_key, $this->generateId($row, $id_keys));
|
||||
}
|
||||
}
|
||||
$entity = $this->getEntity($row, $old_destination_id_values);
|
||||
$entity->save();
|
||||
if (count($ids) > 1) {
|
||||
// This can only be a config entity, content entities have their id key
|
||||
// and that's it.
|
||||
$return = array();
|
||||
foreach ($id_keys as $id_key) {
|
||||
$return[] = $entity->get($id_key);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
return array($entity->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key]['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates an entity with the contents of a row.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to update.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
foreach ($row->getRawDestination() as $property => $value) {
|
||||
$this->updateEntityProperty($entity, explode(Row::PROPERTY_SEPARATOR, $property), $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a (possible nested) entity property with a value.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The config entity.
|
||||
* @param array $parents
|
||||
* The array of parents.
|
||||
* @param string|object $value
|
||||
* The value to update to.
|
||||
*/
|
||||
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
|
||||
$top_key = array_shift($parents);
|
||||
$entity_value = $entity->get($top_key);
|
||||
if (is_array($entity_value)) {
|
||||
NestedArray::setValue($entity_value, $parents, $value);
|
||||
}
|
||||
else {
|
||||
$entity_value = $value;
|
||||
}
|
||||
$entity->set($top_key, $entity_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an entity id.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The current row.
|
||||
* @param array $ids
|
||||
* The destination ids.
|
||||
*
|
||||
* @return string
|
||||
* The generated entity id.
|
||||
*/
|
||||
protected function generateId(Row $row, array $ids) {
|
||||
$id_values = array();
|
||||
foreach ($ids as $id) {
|
||||
$id_values[] = $row->getDestinationProperty($id);
|
||||
}
|
||||
return implode('.', $id_values);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityContentBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* The destination class for all content entities lacking a specific class.
|
||||
*/
|
||||
class EntityContentBase extends Entity {
|
||||
|
||||
/**
|
||||
* Entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs a content entity.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration entity.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager
|
||||
* The plugin manager.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$entity_type = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$entity = $this->getEntity($row, $old_destination_id_values);
|
||||
return $this->save($entity, $old_destination_id_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The content entity.
|
||||
* @param array $old_destination_id_values
|
||||
* An array of destination id values.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the entity id.
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {
|
||||
$entity->save();
|
||||
return array($entity->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key]['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an entity with the new values from row.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to update.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
foreach ($row->getDestination() as $field_name => $values) {
|
||||
$field = $entity->$field_name;
|
||||
if ($field instanceof TypedDataInterface) {
|
||||
$field->setValue($values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityDateFormat.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:date_format"
|
||||
* )
|
||||
*/
|
||||
class EntityDateFormat extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatInterface $entity
|
||||
* The date entity.
|
||||
*/
|
||||
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
|
||||
if ($parents[0] == 'pattern') {
|
||||
$entity->setPattern($value);
|
||||
}
|
||||
else {
|
||||
parent::updateEntityProperty($entity, $parents, $value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityFieldInstance.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:field_config"
|
||||
* )
|
||||
*/
|
||||
class EntityFieldInstance extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['bundle']['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityFieldStorageConfig.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:field_storage_config"
|
||||
* )
|
||||
*/
|
||||
class EntityFieldStorageConfig extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityFile.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\StreamWrapper\LocalStream;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Every migration that uses this destination must have an optional
|
||||
* dependency on the d6_file migration to ensure it runs first.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "entity:file"
|
||||
* )
|
||||
*/
|
||||
class EntityFile extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
|
||||
*/
|
||||
protected $streamWrapperManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) {
|
||||
$configuration += array(
|
||||
'source_base_path' => '',
|
||||
'source_path_property' => 'filepath',
|
||||
'destination_path_property' => 'uri',
|
||||
'move' => FALSE,
|
||||
'urlencode' => FALSE,
|
||||
);
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
|
||||
|
||||
$this->streamWrapperManager = $stream_wrappers;
|
||||
$this->fileSystem = $file_system;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$entity_type = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('stream_wrapper_manager'),
|
||||
$container->get('file_system')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$file = $row->getSourceProperty($this->configuration['source_path_property']);
|
||||
$destination = $row->getDestinationProperty($this->configuration['destination_path_property']);
|
||||
$source = $this->configuration['source_base_path'] . $file;
|
||||
|
||||
// Ensure the source file exists, if it's a local URI or path.
|
||||
if ($this->isLocalUri($source) && !file_exists($source)) {
|
||||
throw new MigrateException(SafeMarkup::format('File @source does not exist.', ['@source' => $source]));
|
||||
}
|
||||
|
||||
// If the start and end file is exactly the same, there is nothing to do.
|
||||
if ($this->isLocationUnchanged($source, $destination)) {
|
||||
return parent::import($row, $old_destination_id_values);
|
||||
}
|
||||
|
||||
$replace = $this->getOverwriteMode($row);
|
||||
$success = $this->writeFile($source, $destination, $replace);
|
||||
if (!$success) {
|
||||
$dir = $this->getDirectory($destination);
|
||||
if (file_prepare_directory($dir, FILE_CREATE_DIRECTORY)) {
|
||||
$success = $this->writeFile($source, $destination, $replace);
|
||||
}
|
||||
else {
|
||||
throw new MigrateException(SafeMarkup::format('Could not create directory @dir', ['@dir' => $dir]));
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
return parent::import($row, $old_destination_id_values);
|
||||
}
|
||||
else {
|
||||
throw new MigrateException(SafeMarkup::format('File %source could not be copied to %destination.', ['%source' => $source, '%destination' => $destination]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to move or copy a file.
|
||||
*
|
||||
* @param string $source
|
||||
* The source path or URI.
|
||||
* @param string $destination
|
||||
* The destination path or URI.
|
||||
* @param integer $replace
|
||||
* FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE on success, FALSE on failure.
|
||||
*/
|
||||
protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) {
|
||||
if ($this->configuration['move']) {
|
||||
return (boolean) file_unmanaged_move($source, $destination, $replace);
|
||||
}
|
||||
else {
|
||||
$destination = file_destination($destination, $replace);
|
||||
$source = $this->urlencode($source);
|
||||
return @copy($source, $destination);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how to handle file conflicts.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
*
|
||||
* @return integer
|
||||
* Either FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME, depending
|
||||
* on the current configuration.
|
||||
*/
|
||||
protected function getOverwriteMode(Row $row) {
|
||||
if (!empty($this->configuration['rename'])) {
|
||||
$entity_id = $row->getDestinationProperty($this->getKey('id'));
|
||||
if ($entity_id && ($entity = $this->storage->load($entity_id))) {
|
||||
return FILE_EXISTS_RENAME;
|
||||
}
|
||||
}
|
||||
return FILE_EXISTS_REPLACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory component of a URI or path.
|
||||
*
|
||||
* For URIs like public://foo.txt, the full physical path of public://
|
||||
* will be returned, since a scheme by itself will trip up certain file
|
||||
* API functions (such as file_prepare_directory()).
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI or path.
|
||||
*
|
||||
* @return boolean|string
|
||||
* The directory component of the path or URI, or FALSE if it could not
|
||||
* be determined.
|
||||
*/
|
||||
protected function getDirectory($uri) {
|
||||
$dir = $this->fileSystem->dirname($uri);
|
||||
if (substr($dir, -3) == '://') {
|
||||
return $this->fileSystem->realpath($dir);
|
||||
}
|
||||
else {
|
||||
return $dir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the source and destination URIs represent identical paths.
|
||||
* If either URI is a remote stream, will return FALSE.
|
||||
*
|
||||
* @param string $source
|
||||
* The source URI.
|
||||
* @param string $destination
|
||||
* The destination URI.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if the source and destination URIs refer to the same physical path,
|
||||
* otherwise FALSE.
|
||||
*/
|
||||
protected function isLocationUnchanged($source, $destination) {
|
||||
if ($this->isLocalUri($source) && $this->isLocalUri($destination)) {
|
||||
return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the given URI or path is considered local.
|
||||
*
|
||||
* A URI or path is considered local if it either has no scheme component,
|
||||
* or the scheme is implemented by a stream wrapper which extends
|
||||
* \Drupal\Core\StreamWrapper\LocalStream.
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI or path to test.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isLocalUri($uri) {
|
||||
$scheme = $this->fileSystem->uriScheme($uri);
|
||||
return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Urlencode all the components of a remote filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* The filename of the file to be urlencoded.
|
||||
*
|
||||
* @return string
|
||||
* The urlencoded filename.
|
||||
*/
|
||||
protected function urlencode($filename) {
|
||||
// Only apply to a full URL
|
||||
if ($this->configuration['urlencode'] && strpos($filename, '://')) {
|
||||
$components = explode('/', $filename);
|
||||
foreach ($components as $key => $component) {
|
||||
$components[$key] = rawurlencode($component);
|
||||
}
|
||||
$filename = implode('/', $components);
|
||||
// Actually, we don't want certain characters encoded
|
||||
$filename = str_replace('%3A', ':', $filename);
|
||||
$filename = str_replace('%3F', '?', $filename);
|
||||
$filename = str_replace('%26', '&', $filename);
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityNodeType.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:node_type"
|
||||
* )
|
||||
*/
|
||||
class EntityNodeType extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$entity_ids = parent::import($row, $old_destination_id_values);
|
||||
if ($row->getDestinationProperty('create_body')) {
|
||||
$node_type = $this->storage->load(reset($entity_ids));
|
||||
node_add_body_field($node_type, $row->getDestinationProperty('create_body_label'));
|
||||
}
|
||||
return $entity_ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityRevision.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity_revision",
|
||||
* deriver = "Drupal\migrate\Plugin\Derivative\MigrateEntityRevision"
|
||||
* )
|
||||
*/
|
||||
class EntityRevision extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getEntityTypeId($plugin_id) {
|
||||
// Remove entity_revision:
|
||||
return substr($plugin_id, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|false
|
||||
* The entity or false if it can not be created.
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
$revision_id = $old_destination_id_values ? reset($old_destination_id_values) : $row->getDestinationProperty($this->getKey('revision'));
|
||||
if (!empty($revision_id) && ($entity = $this->storage->loadRevision($revision_id))) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
}
|
||||
else {
|
||||
$entity_id = $row->getDestinationProperty($this->getKey('id'));
|
||||
$entity = $this->storage->load($entity_id);
|
||||
$entity->enforceIsNew(FALSE);
|
||||
$entity->setNewRevision(TRUE);
|
||||
}
|
||||
$this->updateEntity($entity, $row);
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {
|
||||
$entity->save();
|
||||
return array($entity->getRevisionId());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
if ($key = $this->getKey('revision')) {
|
||||
$ids[$key]['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
throw new MigrateException('This entity type does not support revisions.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntitySearchPage.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:search_page"
|
||||
* )
|
||||
*/
|
||||
class EntitySearchPage extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* Updates the entity with the contents of a row.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The search page entity.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
$entity->setPlugin($row->getDestinationProperty('plugin'));
|
||||
$entity->getPlugin()->setConfiguration($row->getDestinationProperty('configuration'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityTaxonomyTerm.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:taxonomy_term"
|
||||
* )
|
||||
*/
|
||||
class EntityTaxonomyTerm extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
if ($row->isStub()) {
|
||||
$row->setDestinationProperty('name', $this->t('Stub name for source tid:') . $row->getSourceProperty('tid'));
|
||||
}
|
||||
return parent::getEntity($row, $old_destination_id_values);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityUser.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Password\PasswordInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigratePassword;
|
||||
use Drupal\migrate\Plugin\MigratePluginManager;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:user"
|
||||
* )
|
||||
*/
|
||||
class EntityUser extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* The password service class.
|
||||
*
|
||||
* @var \Drupal\Core\Password\PasswordInterface
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* Builds an user entity destination.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager
|
||||
* The migrate plugin manager.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Password\PasswordInterface $password
|
||||
* The password service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, PasswordInterface $password) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
|
||||
if (isset($configuration['md5_passwords'])) {
|
||||
$this->password = $password;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$entity_type = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('password')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
if ($this->password) {
|
||||
if ($this->password instanceof MigratePassword) {
|
||||
$this->password->enableMd5Prefixing();
|
||||
}
|
||||
else {
|
||||
throw new MigrateException('Password service has been altered by another module, aborting.');
|
||||
}
|
||||
}
|
||||
$ids = parent::import($row, $old_destination_id_values);
|
||||
if ($this->password) {
|
||||
$this->password->disableMd5Prefixing();
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityViewMode.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "entity:entity_view_mode"
|
||||
* )
|
||||
*/
|
||||
class EntityViewMode extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['targetEntityType']['type'] = 'string';
|
||||
$ids['mode']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
41
core/modules/migrate/src/Plugin/migrate/destination/Null.php
Normal file
41
core/modules/migrate/src/Plugin/migrate/destination/Null.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\Null.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "null",
|
||||
* requirements_met = false
|
||||
* )
|
||||
*/
|
||||
class Null extends DestinationBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityDisplay.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* This class imports one component of an entity display.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "component_entity_display"
|
||||
* )
|
||||
*/
|
||||
class PerComponentEntityDisplay extends ComponentEntityDisplayBase {
|
||||
|
||||
const MODE_NAME = 'view_mode';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity($entity_type, $bundle, $view_mode) {
|
||||
return entity_get_display($entity_type, $bundle, $view_mode);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityFormDisplay.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* This class imports one component of an entity form display.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "component_entity_form_display"
|
||||
* )
|
||||
*/
|
||||
class PerComponentEntityFormDisplay extends ComponentEntityDisplayBase {
|
||||
|
||||
const MODE_NAME = 'form_mode';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity($entity_type, $bundle, $form_mode) {
|
||||
return entity_get_form_display($entity_type, $bundle, $form_mode);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\UrlAlias.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Path\AliasStorage;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "url_alias"
|
||||
* )
|
||||
*/
|
||||
class UrlAlias extends DestinationBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The alias storage service.
|
||||
*
|
||||
* @var \Drupal\Core\Path\AliasStorage $aliasStorage
|
||||
*/
|
||||
protected $aliasStorage;
|
||||
|
||||
/**
|
||||
* Constructs an entity destination plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\Core\Path\AliasStorage $alias_storage
|
||||
* The alias storage service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, AliasStorage $alias_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->aliasStorage = $alias_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('path.alias_storage')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
|
||||
$path = $this->aliasStorage->save(
|
||||
$row->getDestinationProperty('source'),
|
||||
$row->getDestinationProperty('alias'),
|
||||
$row->getDestinationProperty('langcode'),
|
||||
$old_destination_id_values ? $old_destination_id_values[0] : NULL
|
||||
);
|
||||
|
||||
return array($path['pid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['pid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
return [
|
||||
'pid' => 'The path id',
|
||||
'source' => 'The source path.',
|
||||
'alias' => 'The url alias.',
|
||||
'langcode' => 'The language code for the url.',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\destination\UserData.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\user\UserData as UserDataStorage;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
|
||||
/**
|
||||
* @MigrateDestination(
|
||||
* id = "user_data"
|
||||
* )
|
||||
*/
|
||||
class UserData extends DestinationBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\UserData
|
||||
*/
|
||||
protected $userData;
|
||||
|
||||
/**
|
||||
* Builds an user data entity destination.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\user\UserData $user_data
|
||||
* The user data service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, UserDataStorage $user_data) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->userData = $user_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('user.data')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$uid = $row->getDestinationProperty('uid');
|
||||
$module = $row->getDestinationProperty('module');
|
||||
$key = $row->getDestinationProperty('key');
|
||||
$this->userData->set($module, $uid, $key, $row->getDestinationProperty('settings'));
|
||||
|
||||
return [$uid, $module, $key];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['uid']['type'] = 'integer';
|
||||
$ids['module']['type'] = 'string';
|
||||
$ids['key']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
return [
|
||||
'uid' => 'The user id.',
|
||||
'module' => 'The module name responsible for the settings.',
|
||||
'key' => 'The setting key to save under.',
|
||||
'settings' => 'The settings to save.',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
793
core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
Normal file
793
core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
Normal file
|
@ -0,0 +1,793 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\id_map\Sql.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\id_map;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Defines the sql based ID map implementation.
|
||||
*
|
||||
* It creates one map and one message table per migration entity to store the
|
||||
* relevant information.
|
||||
*
|
||||
* @PluginID("sql")
|
||||
*/
|
||||
class Sql extends PluginBase implements MigrateIdMapInterface {
|
||||
|
||||
/**
|
||||
* The migration map table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mapTableName;
|
||||
|
||||
/**
|
||||
* The message table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $messageTableName;
|
||||
|
||||
/**
|
||||
* The migrate message.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* The database connection for the map/message tables on the destination.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Database\Query\SelectInterface
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* The migration being done.
|
||||
*
|
||||
* @var \Drupal\migrate\Entity\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The source ID fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIdFields;
|
||||
|
||||
/**
|
||||
* The destination ID fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destinationIdFields;
|
||||
|
||||
/**
|
||||
* Whether the plugin is already initialized.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $initialized;
|
||||
|
||||
/**
|
||||
* The result.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
protected $result = NULL;
|
||||
|
||||
/**
|
||||
* The source identifiers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIds = array();
|
||||
|
||||
/**
|
||||
* The destination identifiers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destinationIds = array();
|
||||
|
||||
/**
|
||||
* The current row.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
protected $currentRow = NULL;
|
||||
|
||||
/**
|
||||
* The current key.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $currentKey = array();
|
||||
|
||||
/**
|
||||
* Constructs an SQL object.
|
||||
*
|
||||
* Sets up the tables and builds the maps,
|
||||
*
|
||||
* @param array $configuration
|
||||
* The configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the migration process to do.
|
||||
* @param mixed $plugin_definition
|
||||
* The configuration for the plugin.
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration to do.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* The source ID fields.
|
||||
*
|
||||
* @return array
|
||||
* The source ID fields.
|
||||
*/
|
||||
protected function sourceIdFields() {
|
||||
if (!isset($this->sourceIdFields)) {
|
||||
// Build the source and destination identifier maps.
|
||||
$this->sourceIdFields = array();
|
||||
$count = 1;
|
||||
foreach ($this->migration->getSourcePlugin()->getIds() as $field => $schema) {
|
||||
$this->sourceIdFields[$field] = 'sourceid' . $count++;
|
||||
}
|
||||
}
|
||||
return $this->sourceIdFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* The destination ID fields.
|
||||
*
|
||||
* @return array
|
||||
* The destination ID fields.
|
||||
*/
|
||||
protected function destinationIdFields() {
|
||||
if (!isset($this->destinationIdFields)) {
|
||||
$this->destinationIdFields = array();
|
||||
$count = 1;
|
||||
foreach ($this->migration->getDestinationPlugin()->getIds() as $field => $schema) {
|
||||
$this->destinationIdFields[$field] = 'destid' . $count++;
|
||||
}
|
||||
}
|
||||
return $this->destinationIdFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the database map table.
|
||||
*
|
||||
* @return string
|
||||
* The map table name.
|
||||
*/
|
||||
public function mapTableName() {
|
||||
$this->init();
|
||||
return $this->mapTableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the database message table.
|
||||
*
|
||||
* @return string
|
||||
* The message table name.
|
||||
*/
|
||||
public function messageTableName() {
|
||||
$this->init();
|
||||
return $this->messageTableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified map table name.
|
||||
*
|
||||
* @return string
|
||||
* The fully qualified map table name.
|
||||
*/
|
||||
public function getQualifiedMapTableName() {
|
||||
return $this->getDatabase()->getFullQualifiedTableName($this->mapTableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the database connection.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Connection
|
||||
* The database connection object.
|
||||
*/
|
||||
public function getDatabase() {
|
||||
if (!isset($this->database)) {
|
||||
$this->database = \Drupal::database();
|
||||
}
|
||||
$this->init();
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the plugin.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMessage(MigrateMessageInterface $message) {
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the map and message tables if they don't already exist.
|
||||
*/
|
||||
protected function ensureTables() {
|
||||
if (!$this->getDatabase()->schema()->tableExists($this->mapTableName)) {
|
||||
// Generate appropriate schema info for the map and message tables,
|
||||
// and map from the source field names to the map/msg field names.
|
||||
$count = 1;
|
||||
$source_id_schema = array();
|
||||
$pks = array();
|
||||
foreach ($this->migration->getSourcePlugin()->getIds() as $id_definition) {
|
||||
$mapkey = 'sourceid' . $count++;
|
||||
$source_id_schema[$mapkey] = $this->getFieldSchema($id_definition);
|
||||
|
||||
// With InnoDB, utf8mb4-based primary keys can't be over 191 characters.
|
||||
// Use ASCII-based primary keys instead.
|
||||
if (isset($source_id_schema[$mapkey]['type']) && $source_id_schema[$mapkey]['type'] == 'varchar') {
|
||||
$source_id_schema[$mapkey]['type'] = 'varchar_ascii';
|
||||
}
|
||||
$pks[] = $mapkey;
|
||||
}
|
||||
|
||||
$fields = $source_id_schema;
|
||||
|
||||
// Add destination identifiers to map table.
|
||||
// TODO: How do we discover the destination schema?
|
||||
$count = 1;
|
||||
foreach ($this->migration->getDestinationPlugin()->getIds() as $id_definition) {
|
||||
// Allow dest identifier fields to be NULL (for IGNORED/FAILED
|
||||
// cases).
|
||||
$mapkey = 'destid' . $count++;
|
||||
$fields[$mapkey] = $this->getFieldSchema($id_definition);
|
||||
$fields[$mapkey]['not null'] = FALSE;
|
||||
}
|
||||
$fields['source_row_status'] = array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
'description' => 'Indicates current status of the source row',
|
||||
);
|
||||
$fields['rollback_action'] = array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => MigrateIdMapInterface::ROLLBACK_DELETE,
|
||||
'description' => 'Flag indicating what to do for this item on rollback',
|
||||
);
|
||||
$fields['last_imported'] = array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'UNIX timestamp of the last time this row was imported',
|
||||
);
|
||||
$fields['hash'] = array(
|
||||
'type' => 'varchar',
|
||||
'length' => '64',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Hash of source row data, for detecting changes',
|
||||
);
|
||||
$schema = array(
|
||||
'description' => 'Mappings from source identifier value(s) to destination identifier value(s).',
|
||||
'fields' => $fields,
|
||||
);
|
||||
if ($pks) {
|
||||
$schema['primary key'] = $pks;
|
||||
}
|
||||
$this->getDatabase()->schema()->createTable($this->mapTableName, $schema);
|
||||
|
||||
// Now do the message table.
|
||||
if (!$this->getDatabase()->schema()->tableExists($this->messageTableName())) {
|
||||
$fields = array();
|
||||
$fields['msgid'] = array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$fields += $source_id_schema;
|
||||
|
||||
$fields['level'] = array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
);
|
||||
$fields['message'] = array(
|
||||
'type' => 'text',
|
||||
'size' => 'medium',
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$schema = array(
|
||||
'description' => 'Messages generated during a migration process',
|
||||
'fields' => $fields,
|
||||
'primary key' => array('msgid'),
|
||||
);
|
||||
if ($pks) {
|
||||
$schema['indexes']['sourcekey'] = $pks;
|
||||
}
|
||||
$this->getDatabase()->schema()->createTable($this->messageTableName(), $schema);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Add any missing columns to the map table.
|
||||
if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName,
|
||||
'rollback_action')) {
|
||||
$this->getDatabase()->schema()->addField($this->mapTableName,
|
||||
'rollback_action', array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Flag indicating what to do for this item on rollback',
|
||||
));
|
||||
}
|
||||
if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName, 'hash')) {
|
||||
$this->getDatabase()->schema()->addField($this->mapTableName, 'hash', array(
|
||||
'type' => 'varchar',
|
||||
'length' => '64',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Hash of source row data, for detecting changes',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create schema from an id definition.
|
||||
*
|
||||
* @param array $id_definition
|
||||
* A field schema definition. Can be SQL schema or a type data
|
||||
* based schema. In the latter case, the value of type needs to be
|
||||
* $typed_data_type.$column
|
||||
* @return array
|
||||
*/
|
||||
protected function getFieldSchema(array $id_definition) {
|
||||
$type_parts = explode('.', $id_definition['type']);
|
||||
if (count($type_parts) == 1) {
|
||||
$type_parts[] = 'value';
|
||||
}
|
||||
$schema = BaseFieldDefinition::create($type_parts[0])->getColumns();
|
||||
return $schema[$type_parts[1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRowBySource(array $source_id_values) {
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map');
|
||||
foreach ($this->sourceIdFields() as $source_id) {
|
||||
$query = $query->condition("map.$source_id", array_shift($source_id_values), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
return $result->fetchAssoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRowByDestination(array $destination_id_values) {
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map');
|
||||
foreach ($this->destinationIdFields() as $destination_id) {
|
||||
$query = $query->condition("map.$destination_id", array_shift($destination_id_values), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
return $result->fetchAssoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRowsNeedingUpdate($count) {
|
||||
$rows = array();
|
||||
$result = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map')
|
||||
->condition('source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE)
|
||||
->range(0, $count)
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupSourceID(array $destination_id) {
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map', $this->sourceIdFields());
|
||||
foreach ($this->destinationIdFields() as $key_name) {
|
||||
$query = $query->condition("map.$key_name", array_shift($destination_id), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
$source_id = $result->fetchAssoc();
|
||||
return array_values($source_id ?: array());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupDestinationId(array $source_id) {
|
||||
if (empty($source_id)) {
|
||||
return array();
|
||||
}
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map', $this->destinationIdFields());
|
||||
foreach ($this->sourceIdFields() as $key_name) {
|
||||
$query = $query->condition("map.$key_name", array_shift($source_id), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
$destination_id = $result->fetchAssoc();
|
||||
return array_values($destination_id ?: array());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveIdMapping(Row $row, array $destination_id_values, $source_row_status = MigrateIdMapInterface::STATUS_IMPORTED, $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE) {
|
||||
// Construct the source key.
|
||||
$source_id_values = $row->getSourceIdValues();
|
||||
// Construct the source key and initialize to empty variable keys.
|
||||
$keys = array();
|
||||
foreach ($this->sourceIdFields() as $field_name => $key_name) {
|
||||
// A NULL key value will fail.
|
||||
if (!isset($source_id_values[$field_name])) {
|
||||
$this->message->display(t(
|
||||
'Could not save to map table due to NULL value for key field !field',
|
||||
array('!field' => $field_name)), 'error');
|
||||
return;
|
||||
}
|
||||
$keys[$key_name] = $source_id_values[$field_name];
|
||||
}
|
||||
|
||||
$fields = array(
|
||||
'source_row_status' => (int) $source_row_status,
|
||||
'rollback_action' => (int) $rollback_action,
|
||||
'hash' => $row->getHash(),
|
||||
);
|
||||
$count = 0;
|
||||
foreach ($destination_id_values as $dest_id) {
|
||||
$fields['destid' . ++$count] = $dest_id;
|
||||
}
|
||||
if ($count && $count != count($this->destinationIdFields())) {
|
||||
$this->message->display(t('Could not save to map table due to missing destination id values'), 'error');
|
||||
return;
|
||||
}
|
||||
if ($this->migration->get('trackLastImported')) {
|
||||
$fields['last_imported'] = time();
|
||||
}
|
||||
if ($keys) {
|
||||
$this->getDatabase()->merge($this->mapTableName())
|
||||
->key($keys)
|
||||
->fields($fields)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveMessage(array $source_id_values, $message, $level = MigrationInterface::MESSAGE_ERROR) {
|
||||
$count = 1;
|
||||
foreach ($source_id_values as $id_value) {
|
||||
$fields['sourceid' . $count++] = $id_value;
|
||||
// If any key value is not set, we can't save.
|
||||
if (!isset($id_value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$fields['level'] = $level;
|
||||
$fields['message'] = $message;
|
||||
$this->getDatabase()->insert($this->messageTableName())
|
||||
->fields($fields)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareUpdate() {
|
||||
$this->getDatabase()->update($this->mapTableName())
|
||||
->fields(array('source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processedCount() {
|
||||
return $this->getDatabase()->select($this->mapTableName())
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function importedCount() {
|
||||
return $this->getDatabase()->select($this->mapTableName())
|
||||
->condition('source_row_status', array(MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE), 'IN')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateCount() {
|
||||
return $this->countHelper(MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorCount() {
|
||||
return $this->countHelper(MigrateIdMapInterface::STATUS_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function messageCount() {
|
||||
return $this->countHelper(NULL, $this->messageTableName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts records in a table.
|
||||
*
|
||||
* @param $status
|
||||
* An integer for the source_row_status column.
|
||||
* @param $table
|
||||
* The table to work
|
||||
* @return int
|
||||
* The number of records.
|
||||
*/
|
||||
protected function countHelper($status, $table = NULL) {
|
||||
$query = $this->getDatabase()->select($table ?: $this->mapTableName());
|
||||
if (isset($status)) {
|
||||
$query->condition('source_row_status', $status);
|
||||
}
|
||||
return $query->countQuery()->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(array $source_id_values, $messages_only = FALSE) {
|
||||
if (empty($source_id_values)) {
|
||||
throw new MigrateException('Without source identifier values it is impossible to find the row to delete.');
|
||||
}
|
||||
if (!$messages_only) {
|
||||
$map_query = $this->getDatabase()->delete($this->mapTableName());
|
||||
}
|
||||
$message_query = $this->getDatabase()->delete($this->messageTableName());
|
||||
$count = 1;
|
||||
foreach ($source_id_values as $id_value) {
|
||||
if (!$messages_only) {
|
||||
$map_query->condition('sourceid' . $count, $id_value);
|
||||
}
|
||||
$message_query->condition('sourceid' . $count, $id_value);
|
||||
$count++;
|
||||
}
|
||||
|
||||
if (!$messages_only) {
|
||||
$map_query->execute();
|
||||
}
|
||||
$message_query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteDestination(array $destination_id) {
|
||||
$map_query = $this->getDatabase()->delete($this->mapTableName());
|
||||
$message_query = $this->getDatabase()->delete($this->messageTableName());
|
||||
$source_id = $this->lookupSourceID($destination_id);
|
||||
if (!empty($source_id)) {
|
||||
$count = 1;
|
||||
foreach ($destination_id as $key_value) {
|
||||
$map_query->condition('destid' . $count, $key_value);
|
||||
$count++;
|
||||
}
|
||||
$map_query->execute();
|
||||
$count = 1;
|
||||
foreach ($source_id as $key_value) {
|
||||
$message_query->condition('sourceid' . $count, $key_value);
|
||||
$count++;
|
||||
}
|
||||
$message_query->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUpdate(array $source_id) {
|
||||
if (empty($source_id)) {
|
||||
throw new MigrateException('No source identifiers provided to update.');
|
||||
}
|
||||
$query = $this->getDatabase()
|
||||
->update($this->mapTableName())
|
||||
->fields(array('source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE));
|
||||
$count = 1;
|
||||
foreach ($source_id as $key_value) {
|
||||
$query->condition('sourceid' . $count++, $key_value);
|
||||
}
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteBulk(array $source_id_values) {
|
||||
// If we have a single-column key, we can shortcut it.
|
||||
if (count($this->migration->getSourcePlugin()->getIds()) == 1) {
|
||||
$sourceids = array();
|
||||
foreach ($source_id_values as $source_id) {
|
||||
$sourceids[] = $source_id;
|
||||
}
|
||||
$this->getDatabase()->delete($this->mapTableName())
|
||||
->condition('sourceid1', $sourceids, 'IN')
|
||||
->execute();
|
||||
$this->getDatabase()->delete($this->messageTableName())
|
||||
->condition('sourceid1', $sourceids, 'IN')
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
foreach ($source_id_values as $source_id) {
|
||||
$map_query = $this->getDatabase()->delete($this->mapTableName());
|
||||
$message_query = $this->getDatabase()->delete($this->messageTableName());
|
||||
$count = 1;
|
||||
foreach ($source_id as $key_value) {
|
||||
$map_query->condition('sourceid' . $count, $key_value);
|
||||
$message_query->condition('sourceid' . $count++, $key_value);
|
||||
}
|
||||
$map_query->execute();
|
||||
$message_query->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearMessages() {
|
||||
$this->getDatabase()->truncate($this->messageTableName())->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy() {
|
||||
$this->getDatabase()->schema()->dropTable($this->mapTableName());
|
||||
$this->getDatabase()->schema()->dropTable($this->messageTableName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::rewind().
|
||||
*
|
||||
* This is called before beginning a foreach loop.
|
||||
*
|
||||
* @todo Support idlist, itemlimit.
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->currentRow = NULL;
|
||||
$fields = array();
|
||||
foreach ($this->sourceIdFields() as $field) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
foreach ($this->destinationIdFields() as $field) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
|
||||
// @todo Make this work.
|
||||
/*
|
||||
if (isset($this->options['itemlimit'])) {
|
||||
$query = $query->range(0, $this->options['itemlimit']);
|
||||
}
|
||||
*/
|
||||
$this->result = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map', $fields)
|
||||
->execute();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::current().
|
||||
*
|
||||
* This is called when entering a loop iteration, returning the current row.
|
||||
*/
|
||||
public function current() {
|
||||
return $this->currentRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::key().
|
||||
*
|
||||
* This is called when entering a loop iteration, returning the key of the
|
||||
* current row. It must be a scalar - we will serialize to fulfill the
|
||||
* requirement, but using getCurrentKey() is preferable.
|
||||
*/
|
||||
public function key() {
|
||||
return serialize($this->currentKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::next().
|
||||
*
|
||||
* This is called at the bottom of the loop implicitly, as well as explicitly
|
||||
* from rewind().
|
||||
*/
|
||||
public function next() {
|
||||
$this->currentRow = $this->result->fetchAssoc();
|
||||
$this->currentKey = array();
|
||||
if ($this->currentRow) {
|
||||
foreach ($this->sourceIdFields() as $map_field) {
|
||||
$this->currentKey[$map_field] = $this->currentRow[$map_field];
|
||||
// Leave only destination fields.
|
||||
unset($this->currentRow[$map_field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::valid().
|
||||
*
|
||||
* This is called at the top of the loop, returning TRUE to process the loop
|
||||
* and FALSE to terminate it.
|
||||
*/
|
||||
public function valid() {
|
||||
// @todo Check numProcessed against itemlimit.
|
||||
return $this->currentRow !== FALSE;
|
||||
}
|
||||
|
||||
}
|
39
core/modules/migrate/src/Plugin/migrate/process/Callback.php
Normal file
39
core/modules/migrate/src/Plugin/migrate/process/Callback.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Callback.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin allows source value to be passed to a callback.
|
||||
*
|
||||
* The current value is passed to a callable that returns the processed value.
|
||||
* This plugin allows simple processing of the value, such as strtolower(). The
|
||||
* callable takes the value as the single mandatory argument. No additional
|
||||
* arguments can be passed to the callback as this would make the migration YAML
|
||||
* file too complex.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "callback"
|
||||
* )
|
||||
*/
|
||||
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);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
41
core/modules/migrate/src/Plugin/migrate/process/Concat.php
Normal file
41
core/modules/migrate/src/Plugin/migrate/process/Concat.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Concat.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Concatenates the strings in the current value.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "concat",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class Concat extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Concatenates the strings in the current value.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (is_array($value)) {
|
||||
$delimiter = isset($this->configuration['delimiter']) ? $this->configuration['delimiter'] : '';
|
||||
return implode($delimiter, $value);
|
||||
}
|
||||
else {
|
||||
throw new MigrateException(sprintf('%s is not an array', SafeMarkup::checkPlain(var_export($value, TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\DedupeBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* This abstract base contains the dedupe logic.
|
||||
*
|
||||
* These plugins avoid duplication at the destination. For example, when
|
||||
* creating filter format names, the current value is checked against the
|
||||
* existing filter format names and if it exists, a numeric postfix is added
|
||||
* and incremented until a unique value is created.
|
||||
*/
|
||||
abstract class DedupeBase extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$i = 1;
|
||||
$postfix = isset($this->configuration['postfix']) ? $this->configuration['postfix'] : '';
|
||||
$start = isset($this->configuration['start']) ? $this->configuration['start'] : 0;
|
||||
if (!is_int($start)) {
|
||||
throw new MigrateException('The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.');
|
||||
}
|
||||
$length = isset($this->configuration['length']) ? $this->configuration['length'] : NULL;
|
||||
if (!is_null($length) && !is_int($length)) {
|
||||
throw new MigrateException('The character length configuration key should be an integer. Omit this key to capture the entire string.');
|
||||
}
|
||||
// Use optional start or length to return a portion of deduplicated value.
|
||||
$value = Unicode::substr($value, $start, $length);
|
||||
$new_value = $value;
|
||||
while ($this->exists($new_value)) {
|
||||
$new_value = $value . $postfix . $i++;
|
||||
}
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a query checking the existence of some value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The value to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the value exists.
|
||||
*/
|
||||
abstract protected function exists($value);
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\DedupeEntity.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Ensures value is not duplicated against an entity field.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "dedupe_entity"
|
||||
* )
|
||||
*/
|
||||
class DedupeEntity extends DedupeBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactoryInterface
|
||||
*/
|
||||
protected $entityQueryFactory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, QueryFactory $entity_query_factory) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityQueryFactory = $entity_query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity.query')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function exists($value) {
|
||||
// Plugins are cached so for every run we need a new query object.
|
||||
return $this
|
||||
->entityQueryFactory
|
||||
->get($this->configuration['entity_type'], 'AND')
|
||||
->condition($this->configuration['field'], $value)
|
||||
->count()
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\DefaultValue.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
|
||||
/**
|
||||
* This plugin sets missing values on the destination.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "default_value"
|
||||
* )
|
||||
*/
|
||||
class DefaultValue extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!empty($this->configuration['strict'])) {
|
||||
return isset($value) ? $value : $this->configuration['default_value'];
|
||||
}
|
||||
return $value ?: $this->configuration['default_value'];
|
||||
}
|
||||
}
|
41
core/modules/migrate/src/Plugin/migrate/process/Extract.php
Normal file
41
core/modules/migrate/src/Plugin/migrate/process/Extract.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Extract.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin extracts a value from an array.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2152731
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "extract"
|
||||
* )
|
||||
*/
|
||||
class Extract extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!is_array($value)) {
|
||||
throw new MigrateException('Input should be an array.');
|
||||
}
|
||||
$new_value = NestedArray::getValue($value, $this->configuration['index'], $key_exists);
|
||||
if (!$key_exists) {
|
||||
throw new MigrateException('Array index missing, extraction failed.');
|
||||
}
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
37
core/modules/migrate/src/Plugin/migrate/process/Flatten.php
Normal file
37
core/modules/migrate/src/Plugin/migrate/process/Flatten.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Flatten.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin flattens the current value.
|
||||
*
|
||||
* During some types of processing (e.g. user permission splitting), what was
|
||||
* once a single value gets transformed into multiple values. This plugin will
|
||||
* flatten them back down to single values again.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2154215
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "flatten",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class Flatten extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Flatten nested array values to single array values.
|
||||
*
|
||||
* For example, array(array(1, 2, array(3, 4))) becomes array(1, 2, 3, 4).
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
return iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($value)), FALSE);
|
||||
}
|
||||
}
|
72
core/modules/migrate/src/Plugin/migrate/process/Get.php
Normal file
72
core/modules/migrate/src/Plugin/migrate/process/Get.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Get.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin copies from the source to the destination.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "get"
|
||||
* )
|
||||
*/
|
||||
class Get extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $multiple;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$source = $this->configuration['source'];
|
||||
$properties = is_string($source) ? array($source) : $source;
|
||||
$return = array();
|
||||
foreach ($properties as $property) {
|
||||
if (empty($property)) {
|
||||
$return[] = $value;
|
||||
}
|
||||
else {
|
||||
$is_source = TRUE;
|
||||
if ($property[0] == '@') {
|
||||
$property = preg_replace_callback('/^(@?)((?:@@)*)([^@]|$)/', function ($matches) use (&$is_source) {
|
||||
// If there are an odd number of @ in the beginning, it's a
|
||||
// destination.
|
||||
$is_source = empty($matches[1]);
|
||||
// Remove the possible escaping and do not lose the terminating
|
||||
// non-@ either.
|
||||
return str_replace('@@', '@', $matches[2]) . $matches[3];
|
||||
}, $property);
|
||||
}
|
||||
if ($is_source) {
|
||||
$return[] = $row->getSourceProperty($property);
|
||||
}
|
||||
else {
|
||||
$return[] = $row->getDestinationProperty($property);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_string($source)) {
|
||||
$this->multiple = is_array($return[0]);
|
||||
return $return[0];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function multiple() {
|
||||
return $this->multiple;
|
||||
}
|
||||
}
|
68
core/modules/migrate/src/Plugin/migrate/process/Iterator.php
Normal file
68
core/modules/migrate/src/Plugin/migrate/process/Iterator.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Iterator.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin iterates and processes an array.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2135345
|
||||
*
|
||||
* @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 = array();
|
||||
foreach ($value as $key => $new_value) {
|
||||
$new_row = new Row($new_value, array());
|
||||
$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 = array('key' => $this->configuration['key']);
|
||||
$migrate_executable->processRow($row, $process, $key);
|
||||
return $row->getDestinationProperty('key');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function multiple() {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\MachineName.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Transliteration\TransliterationInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This plugin creates a machine name.
|
||||
*
|
||||
* The current value gets transliterated, non-alphanumeric characters removed
|
||||
* and replaced by an underscore and multiple underscores are collapsed into
|
||||
* one.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "machine_name"
|
||||
* )
|
||||
*/
|
||||
class MachineName extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Component\Transliteration\TransliterationInterface
|
||||
*/
|
||||
protected $transliteration;
|
||||
|
||||
/**
|
||||
* Constructs a MachineName plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
|
||||
* The transliteration service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, TransliterationInterface $transliteration) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->transliteration = $transliteration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('transliteration')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$new_value = $this->transliteration->transliterate($value, LanguageInterface::LANGCODE_DEFAULT, '_');
|
||||
$new_value = strtolower($new_value);
|
||||
$new_value = preg_replace('/[^a-z0-9_]+/', '_', $new_value);
|
||||
return preg_replace('/_+/', '_', $new_value);
|
||||
}
|
||||
|
||||
}
|
165
core/modules/migrate/src/Plugin/migrate/process/Migration.php
Normal file
165
core/modules/migrate/src/Plugin/migrate/process/Migration.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Migration.
|
||||
*/
|
||||
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateSkipProcessException;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\migrate\Plugin\MigratePluginManager;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Calculates the value of a property based on a previous migration.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "migration"
|
||||
* )
|
||||
*/
|
||||
class Migration extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $processPluginManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $migrationStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, MigratePluginManager $process_plugin_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migrationStorage = $storage;
|
||||
$this->migration = $migration;
|
||||
$this->processPluginManager = $process_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity.manager')->getStorage('migration'),
|
||||
$container->get('plugin.manager.migrate.process')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$migration_ids = $this->configuration['migration'];
|
||||
if (!is_array($migration_ids)) {
|
||||
$migration_ids = array($migration_ids);
|
||||
}
|
||||
$scalar = FALSE;
|
||||
if (!is_array($value)) {
|
||||
$scalar = TRUE;
|
||||
$value = array($value);
|
||||
}
|
||||
$this->skipOnEmpty($value);
|
||||
$self = FALSE;
|
||||
/** @var \Drupal\migrate\Entity\MigrationInterface[] $migrations */
|
||||
$migrations = $this->migrationStorage->loadMultiple($migration_ids);
|
||||
$destination_ids = NULL;
|
||||
$source_id_values = array();
|
||||
foreach ($migrations as $migration_id => $migration) {
|
||||
if ($migration_id == $this->migration->id()) {
|
||||
$self = TRUE;
|
||||
}
|
||||
if (isset($this->configuration['source_ids'][$migration_id])) {
|
||||
$configuration = array('source' => $this->configuration['source_ids'][$migration_id]);
|
||||
$source_id_values[$migration_id] = $this->processPluginManager
|
||||
->createInstance('get', $configuration, $this->migration)
|
||||
->transform(NULL, $migrate_executable, $row, $destination_property);
|
||||
}
|
||||
else {
|
||||
$source_id_values[$migration_id] = $value;
|
||||
}
|
||||
// Break out of the loop as soon as a destination ID is found.
|
||||
if ($destination_ids = $migration->getIdMap()->lookupDestinationID($source_id_values[$migration_id])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$destination_ids && ($self || isset($this->configuration['stub_id']) || count($migrations) == 1)) {
|
||||
// If the lookup didn't succeed, figure out which migration will do the
|
||||
// stubbing.
|
||||
if ($self) {
|
||||
$migration = $this->migration;
|
||||
}
|
||||
elseif (isset($this->configuration['stub_id'])) {
|
||||
$migration = $migrations[$this->configuration['stub_id']];
|
||||
}
|
||||
else {
|
||||
$migration = reset($migrations);
|
||||
}
|
||||
$destination_plugin = $migration->getDestinationPlugin(TRUE);
|
||||
// Only keep the process necessary to produce the destination ID.
|
||||
$process = $migration->get('process');
|
||||
// We already have the source id values but need to key them for the Row
|
||||
// constructor.
|
||||
$source_ids = $migration->getSourcePlugin()->getIds();
|
||||
$values = array();
|
||||
foreach (array_keys($source_ids) as $index => $source_id) {
|
||||
$values[$source_id] = $source_id_values[$migration->id()][$index];
|
||||
}
|
||||
|
||||
$stub_row = new Row($values + $migration->get('source'), $source_ids, TRUE);
|
||||
|
||||
// Do a normal migration with the stub row.
|
||||
$migrate_executable->processRow($stub_row, $process);
|
||||
$destination_ids = array();
|
||||
try {
|
||||
$destination_ids = $destination_plugin->import($stub_row);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$migrate_executable->saveMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
if ($destination_ids) {
|
||||
if ($scalar) {
|
||||
if (count($destination_ids) == 1) {
|
||||
return reset($destination_ids);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return $destination_ids;
|
||||
}
|
||||
}
|
||||
throw new MigrateSkipRowException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the migration process entirely if the value is FALSE.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The incoming value to transform.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
*/
|
||||
protected function skipOnEmpty($value) {
|
||||
if (!array_filter($value)) {
|
||||
throw new MigrateSkipProcessException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
93
core/modules/migrate/src/Plugin/migrate/process/Route.php
Normal file
93
core/modules/migrate/src/Plugin/migrate/process/Route.php
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\Route.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\Core\Path\PathValidatorInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "route"
|
||||
* )
|
||||
*/
|
||||
class Route extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Path\PathValidatorInterface
|
||||
*/
|
||||
protected $pathValidator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, PathValidatorInterface $pathValidator) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
$this->pathValidator = $pathValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('path.validator')
|
||||
);
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Set the destination route information based on the source link_path.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
list($link_path, $options) = $value;
|
||||
$extracted = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link_path);
|
||||
$route = array();
|
||||
|
||||
if ($extracted) {
|
||||
if ($extracted->isExternal()) {
|
||||
$route['route_name'] = null;
|
||||
$route['route_parameters'] = array();
|
||||
$route['options'] = $options;
|
||||
$route['url'] = $extracted->getUri();
|
||||
}
|
||||
else {
|
||||
$route['route_name'] = $extracted->getRouteName();
|
||||
$route['route_parameters'] = $extracted->getRouteParameters();
|
||||
$route['options'] = $extracted->getOptions();
|
||||
|
||||
if (isset($options['query'])) {
|
||||
// If the querystring is stored as a string (as in D6), convert it
|
||||
// into an array.
|
||||
if (is_string($options['query'])) {
|
||||
parse_str($options['query'], $old_query);
|
||||
}
|
||||
else {
|
||||
$old_query = $options['query'];
|
||||
}
|
||||
$options['query'] = $route['options']['query'] + $old_query;
|
||||
unset($route['options']['query']);
|
||||
}
|
||||
$route['options'] = $route['options'] + $options;
|
||||
$route['url'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\SkipOnEmpty.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateSkipProcessException;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* If the source evaluates to empty, we skip processing or the whole row.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "skip_on_empty"
|
||||
* )
|
||||
*/
|
||||
class SkipOnEmpty extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function row($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!$value) {
|
||||
throw new MigrateSkipRowException();
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!$value) {
|
||||
throw new MigrateSkipProcessException();
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\SkipRowIfNotSet.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* If the source evaluates to empty, we skip the current row.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "skip_row_if_not_set",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class SkipRowIfNotSet extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!isset($value[$this->configuration['index']])) {
|
||||
throw new MigrateSkipRowException();
|
||||
}
|
||||
return $value[$this->configuration['index']];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\process\StaticMap.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* This plugin changes the current value based on a static lookup map.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2143521
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "static_map"
|
||||
* )
|
||||
*/
|
||||
class StaticMap extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$new_value = $value;
|
||||
if (is_array($value)) {
|
||||
if (!$value) {
|
||||
throw new MigrateException('Can not lookup without a value.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$new_value = array($value);
|
||||
}
|
||||
$new_value = NestedArray::getValue($this->configuration['map'], $new_value, $key_exists);
|
||||
if (!$key_exists) {
|
||||
if (isset($this->configuration['default_value'])) {
|
||||
if (!empty($this->configuration['bypass'])) {
|
||||
throw new MigrateException('Setting both default_value and bypass is invalid.');
|
||||
}
|
||||
return $this->configuration['default_value'];
|
||||
}
|
||||
if (empty($this->configuration['bypass'])) {
|
||||
throw new MigrateSkipRowException();
|
||||
}
|
||||
else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\source\EmptySource.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
/**
|
||||
* Source returning an empty row.
|
||||
*
|
||||
* This is generally useful when needing to create a field using a migration..
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "empty"
|
||||
* )
|
||||
*/
|
||||
class EmptySource extends SourcePluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return array(
|
||||
'id' => t('ID'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initializeIterator() {
|
||||
return new \ArrayIterator(array(array('id' => '')));
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['id']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,458 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\source\SourcePluginBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\MigrateSourceInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* The base class for all source plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
abstract class SourcePluginBase extends PluginBase implements MigrateSourceInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Entity\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The name and type of the highwater property in the source.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see $originalHighwater
|
||||
*/
|
||||
protected $highWaterProperty;
|
||||
|
||||
/**
|
||||
* The current row from the query
|
||||
*
|
||||
* @var \Drupal\Migrate\Row
|
||||
*/
|
||||
protected $currentRow;
|
||||
|
||||
/**
|
||||
* The primary key of the current row
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $currentSourceIds;
|
||||
|
||||
/**
|
||||
* Number of rows intentionally ignored (prepareRow() returned FALSE)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $numIgnored = 0;
|
||||
|
||||
/**
|
||||
* Number of rows we've at least looked at.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $numProcessed = 0;
|
||||
|
||||
/**
|
||||
* The high water mark at the beginning of the import operation.
|
||||
*
|
||||
* If the source has a property for tracking changes (like Drupal ha
|
||||
* node.changed) then this is the highest value of those imported so far.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $originalHighWater;
|
||||
|
||||
/**
|
||||
* List of source IDs to process.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $idList = array();
|
||||
|
||||
/**
|
||||
* Whether this instance should cache the source count.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $cacheCounts = FALSE;
|
||||
|
||||
/**
|
||||
* Key to use for caching counts.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheKey;
|
||||
|
||||
/**
|
||||
* Whether this instance should not attempt to count the source.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $skipCount = FALSE;
|
||||
|
||||
/**
|
||||
* If TRUE, we will maintain hashed source rows to determine whether incoming
|
||||
* data has changed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $trackChanges = FALSE;
|
||||
|
||||
/**
|
||||
* By default, next() will directly read the map row and add it to the data
|
||||
* row. A source plugin implementation may do this itself (in particular, the
|
||||
* SQL source can incorporate the map table into the query) - if so, it should
|
||||
* set this TRUE so we don't duplicate the effort.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $mapRowAdded = FALSE;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
*/
|
||||
protected $idMap;
|
||||
|
||||
/**
|
||||
* @var \Iterator
|
||||
*/
|
||||
protected $iterator;
|
||||
|
||||
// @TODO, find out how to remove this.
|
||||
// @see https://www.drupal.org/node/2443617
|
||||
public $migrateExecutable;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
|
||||
// Set up some defaults based on the source configuration.
|
||||
$this->cacheCounts = !empty($configuration['cache_counts']);
|
||||
$this->skipCount = !empty($configuration['skip_count']);
|
||||
$this->cacheKey = !empty($configuration['cache_key']) ? !empty($configuration['cache_key']) : NULL;
|
||||
$this->trackChanges = !empty($configuration['track_changes']) ? $configuration['track_changes'] : FALSE;
|
||||
|
||||
// Pull out the current highwater mark if we have a highwater property.
|
||||
if ($this->highWaterProperty = $this->migration->get('highWaterProperty')) {
|
||||
$this->originalHighWater = $this->migration->getHighWater();
|
||||
}
|
||||
|
||||
if ($id_list = $this->migration->get('idlist')) {
|
||||
$this->idList = $id_list;
|
||||
}
|
||||
|
||||
// Don't allow the use of both highwater and track changes together.
|
||||
if ($this->highWaterProperty && $this->trackChanges) {
|
||||
throw new MigrateException('You should either use a highwater mark or track changes not both. They are both designed to solve the same problem');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the iterator with the source data.
|
||||
*
|
||||
* @return array
|
||||
* An array of the data for this source.
|
||||
*/
|
||||
protected abstract function initializeIterator();
|
||||
|
||||
/**
|
||||
* Get the module handler.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
* The module handler.
|
||||
*/
|
||||
protected function getModuleHandler() {
|
||||
if (!isset($this->moduleHandler)) {
|
||||
$this->moduleHandler = \Drupal::moduleHandler();
|
||||
}
|
||||
return $this->moduleHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
|
||||
$result = TRUE;
|
||||
$result_hook = $this->getModuleHandler()->invokeAll('migrate_prepare_row', array($row, $this, $this->migration));
|
||||
$result_named_hook = $this->getModuleHandler()->invokeAll('migrate_' . $this->migration->id() . '_prepare_row', array($row, $this, $this->migration));
|
||||
|
||||
// We're explicitly skipping this row - keep track in the map table.
|
||||
if (($result_hook && in_array(FALSE, $result_hook)) || ($result_named_hook && in_array(FALSE, $result_named_hook))) {
|
||||
// Make sure we replace any previous messages for this item with any
|
||||
// new ones.
|
||||
$id_map = $this->migration->getIdMap();
|
||||
$id_map->delete($this->currentSourceIds, TRUE);
|
||||
$this->migrateExecutable->saveQueuedMessages();
|
||||
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED, $this->migrateExecutable->rollbackAction);
|
||||
$this->numIgnored++;
|
||||
$this->currentRow = NULL;
|
||||
$this->currentSourceIds = NULL;
|
||||
$result = FALSE;
|
||||
}
|
||||
elseif ($this->trackChanges) {
|
||||
// When tracking changed data, We want to quietly skip (rather than
|
||||
// "ignore") rows with changes. The caller needs to make that decision,
|
||||
// so we need to provide them with the necessary information (before and
|
||||
// after hashes).
|
||||
$row->rehash();
|
||||
}
|
||||
$this->numProcessed++;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the iterator that will yield the row arrays to be processed.
|
||||
*
|
||||
* @return \Iterator
|
||||
*/
|
||||
public function getIterator() {
|
||||
if (!isset($this->iterator)) {
|
||||
$this->iterator = $this->initializeIterator();
|
||||
}
|
||||
return $this->iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current() {
|
||||
return $this->currentRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the iterator key.
|
||||
*
|
||||
* Implementation of Iterator::key - called when entering a loop iteration,
|
||||
* returning the key of the current row. It must be a scalar - we will
|
||||
* serialize to fulfill the requirement, but using getCurrentIds() is
|
||||
* preferable.
|
||||
*/
|
||||
public function key() {
|
||||
return serialize($this->currentSourceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the iterator is currently valid.
|
||||
*
|
||||
* Implementation of Iterator::valid() - called at the top of the loop,
|
||||
* returning TRUE to process the loop and FALSE to terminate it
|
||||
*/
|
||||
public function valid() {
|
||||
return isset($this->currentRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the iterator.
|
||||
*
|
||||
* Implementation of Iterator::rewind() - subclasses of MigrateSource should
|
||||
* implement performRewind() to do any class-specific setup for iterating
|
||||
* source records.
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->idMap = $this->migration->getIdMap();
|
||||
$this->numProcessed = 0;
|
||||
$this->numIgnored = 0;
|
||||
$this->getIterator()->rewind();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* The migration iterates over rows returned by the source plugin. This
|
||||
* method determines the next row which will be processed and imported into
|
||||
* the system.
|
||||
*
|
||||
* The method tracks the source and destination IDs using the ID map plugin.
|
||||
*
|
||||
* This also takes care about highwater support. Highwater allows to reimport
|
||||
* rows from a previous migration run, which got changed in the meantime.
|
||||
* This is done by specifying a highwater field, which is compared with the
|
||||
* last time, the migration got executed (originalHighWater).
|
||||
*/
|
||||
public function next() {
|
||||
$this->currentSourceIds = NULL;
|
||||
$this->currentRow = NULL;
|
||||
|
||||
// In order to find the next row we want to process, we ask the source
|
||||
// plugin for the next possible row.
|
||||
while (!isset($this->currentRow) && $this->getIterator()->valid()) {
|
||||
|
||||
$row_data = $this->getIterator()->current() + $this->configuration;
|
||||
$this->getIterator()->next();
|
||||
$row = new Row($row_data, $this->migration->getSourcePlugin()->getIds(), $this->migration->get('destinationIds'));
|
||||
|
||||
// Populate the source key for this row.
|
||||
$this->currentSourceIds = $row->getSourceIdValues();
|
||||
|
||||
// Pick up the existing map row, if any, unless getNextRow() did it.
|
||||
if (!$this->mapRowAdded && ($id_map = $this->idMap->getRowBySource($this->currentSourceIds))) {
|
||||
$row->setIdMap($id_map);
|
||||
}
|
||||
|
||||
// In case we have specified an ID list, but the ID given by the source is
|
||||
// not in there, we skip the row.
|
||||
$id_in_the_list = $this->idList && in_array(reset($this->currentSourceIds), $this->idList);
|
||||
if ($this->idList && !$id_in_the_list) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Preparing the row gives source plugins the chance to skip.
|
||||
if ($this->prepareRow($row) === FALSE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check whether the row needs processing.
|
||||
// 1. Explicitly specified IDs.
|
||||
// 2. This row has not been imported yet.
|
||||
// 3. Explicitly set to update.
|
||||
// 4. The row is newer than the current highwater mark.
|
||||
// 5. If no such property exists then try by checking the hash of the row.
|
||||
if ($id_in_the_list || !$row->getIdMap() || $row->needsUpdate() || $this->aboveHighwater($row) || $this->rowChanged($row) ) {
|
||||
$this->currentRow = $row->freezeSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the incoming data is newer than what we've previously imported.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row we're importing.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the highwater value in the row is greater than our current value.
|
||||
*/
|
||||
protected function aboveHighwater(Row $row) {
|
||||
return $this->highWaterProperty && $row->getSourceProperty($this->highWaterProperty['name']) > $this->originalHighWater;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the incoming row has changed since our last import.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row we're importing.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the row has changed otherwise FALSE.
|
||||
*/
|
||||
protected function rowChanged(Row $row) {
|
||||
return $this->trackChanges && $row->changed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for currentSourceIds data member.
|
||||
*/
|
||||
public function getCurrentIds() {
|
||||
return $this->currentSourceIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for numIgnored data member.
|
||||
*/
|
||||
public function getIgnored() {
|
||||
return $this->numIgnored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for numProcessed data member.
|
||||
*/
|
||||
public function getProcessed() {
|
||||
return $this->numProcessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset numIgnored back to 0.
|
||||
*/
|
||||
public function resetStats() {
|
||||
$this->numIgnored = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source count.
|
||||
*
|
||||
* Return a count of available source records, from the cache if appropriate.
|
||||
* Returns -1 if the source is not countable.
|
||||
*
|
||||
* @param bool $refresh
|
||||
* Whether or not to refresh the count.
|
||||
*
|
||||
* @return int
|
||||
* The count.
|
||||
*/
|
||||
public function count($refresh = FALSE) {
|
||||
if ($this->skipCount) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!isset($this->cacheKey)) {
|
||||
$this->cacheKey = hash('sha256', $this->getPluginId());
|
||||
}
|
||||
|
||||
// If a refresh is requested, or we're not caching counts, ask the derived
|
||||
// class to get the count from the source.
|
||||
if ($refresh || !$this->cacheCounts) {
|
||||
$count = $this->getIterator()->count();
|
||||
$this->getCache()->set($this->cacheKey, $count, 'cache');
|
||||
}
|
||||
else {
|
||||
// Caching is in play, first try to retrieve a cached count.
|
||||
$cache_object = $this->getCache()->get($this->cacheKey, 'cache');
|
||||
if (is_object($cache_object)) {
|
||||
// Success.
|
||||
$count = $cache_object->data;
|
||||
}
|
||||
else {
|
||||
// No cached count, ask the derived class to count 'em up, and cache
|
||||
// the result.
|
||||
$count = $this->getIterator()->count();
|
||||
$this->getCache()->set($this->cacheKey, $count, 'cache');
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache object.
|
||||
*
|
||||
* @return \Drupal\Core\Cache\CacheBackendInterface
|
||||
* The cache object.
|
||||
*/
|
||||
protected function getCache() {
|
||||
if (!isset($this->cache)) {
|
||||
$this->cache = \Drupal::cache('migrate');
|
||||
}
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
}
|
235
core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
Normal file
235
core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
Normal file
|
@ -0,0 +1,235 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Plugin\migrate\source\SqlBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\migrate\id_map\Sql;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
|
||||
/**
|
||||
* Sources whose data may be fetched via DBTNG.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
abstract class SqlBase extends SourcePluginBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Database\Query\SelectInterface
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Entity\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the query string when the object is used a string.
|
||||
*
|
||||
* @return string
|
||||
* The query string.
|
||||
*/
|
||||
public function __toString() {
|
||||
return (string) $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection object.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Connection
|
||||
* The database connection.
|
||||
*/
|
||||
public function getDatabase() {
|
||||
if (!isset($this->database)) {
|
||||
if (isset($this->configuration['target'])) {
|
||||
$target = $this->configuration['target'];
|
||||
}
|
||||
else {
|
||||
$target = 'default';
|
||||
}
|
||||
if (isset($this->configuration['key'])) {
|
||||
$key = $this->configuration['key'];
|
||||
}
|
||||
else {
|
||||
$key = 'migrate';
|
||||
}
|
||||
if (isset($this->configuration['database'])) {
|
||||
Database::addConnectionInfo($key, $target, $this->configuration['database']);
|
||||
}
|
||||
$this->database = Database::getConnection($target, $key);
|
||||
}
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for database select.
|
||||
*/
|
||||
protected function select($table, $alias = NULL, array $options = array()) {
|
||||
$options['fetch'] = \PDO::FETCH_ASSOC;
|
||||
return $this->getDatabase()->select($table, $alias, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for adding tags and metadata to the query.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\SelectInterface
|
||||
* The query with additional tags and metadata.
|
||||
*/
|
||||
protected function prepareQuery() {
|
||||
$this->query = clone $this->query();
|
||||
$this->query->addTag('migrate');
|
||||
$this->query->addTag('migrate_' . $this->migration->id());
|
||||
$this->query->addMetaData('migration', $this->migration);
|
||||
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::performRewind().
|
||||
*
|
||||
* We could simply execute the query and be functionally correct, but
|
||||
* we will take advantage of the PDO-based API to optimize the query up-front.
|
||||
*/
|
||||
protected function initializeIterator() {
|
||||
$this->prepareQuery();
|
||||
$high_water_property = $this->migration->get('highWaterProperty');
|
||||
|
||||
// Get the key values, for potential use in joining to the map table, or
|
||||
// enforcing idlist.
|
||||
$keys = array();
|
||||
|
||||
// The rules for determining what conditions to add to the query are as
|
||||
// follows (applying first applicable rule)
|
||||
// 1. If idlist is provided, then only process items in that list (AND key
|
||||
// IN (idlist)). Only applicable with single-value keys.
|
||||
if ($id_list = $this->migration->get('idlist')) {
|
||||
$this->query->condition($keys[0], $id_list, 'IN');
|
||||
}
|
||||
else {
|
||||
// 2. If the map is joinable, join it. We will want to accept all rows
|
||||
// which are either not in the map, or marked in the map as NEEDS_UPDATE.
|
||||
// Note that if high water fields are in play, we want to accept all rows
|
||||
// above the high water mark in addition to those selected by the map
|
||||
// conditions, so we need to OR them together (but AND with any existing
|
||||
// conditions in the query). So, ultimately the SQL condition will look
|
||||
// like (original conditions) AND (map IS NULL OR map needs update
|
||||
// OR above high water).
|
||||
$conditions = $this->query->orConditionGroup();
|
||||
$condition_added = FALSE;
|
||||
if ($this->mapJoinable()) {
|
||||
// Build the join to the map table. Because the source key could have
|
||||
// multiple fields, we need to build things up.
|
||||
$count = 1;
|
||||
$map_join = '';
|
||||
$delimiter = '';
|
||||
foreach ($this->getIds() as $field_name => $field_schema) {
|
||||
if (isset($field_schema['alias'])) {
|
||||
$field_name = $field_schema['alias'] . '.' . $field_name;
|
||||
}
|
||||
$map_join .= "$delimiter$field_name = map.sourceid" . $count++;
|
||||
$delimiter = ' AND ';
|
||||
}
|
||||
|
||||
$alias = $this->query->leftJoin($this->migration->getIdMap()->getQualifiedMapTableName(), 'map', $map_join);
|
||||
$conditions->isNull($alias . '.sourceid1');
|
||||
$conditions->condition($alias . '.source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
$condition_added = TRUE;
|
||||
|
||||
// And as long as we have the map table, add its data to the row.
|
||||
$n = count($this->getIds());
|
||||
for ($count = 1; $count <= $n; $count++) {
|
||||
$map_key = 'sourceid' . $count;
|
||||
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
|
||||
}
|
||||
if ($n = count($this->migration->get('destinationIds'))) {
|
||||
for ($count = 1; $count <= $n; $count++) {
|
||||
$map_key = 'destid' . $count++;
|
||||
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
|
||||
}
|
||||
}
|
||||
$this->query->addField($alias, 'source_row_status', 'migrate_map_source_row_status');
|
||||
}
|
||||
// 3. 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 (isset($high_water_property['name']) && ($high_water = $this->migration->getHighWater()) !== '') {
|
||||
if (isset($high_water_property['alias'])) {
|
||||
$high_water = $high_water_property['alias'] . '.' . $high_water_property['name'];
|
||||
}
|
||||
else {
|
||||
$high_water = $high_water_property['name'];
|
||||
}
|
||||
$conditions->condition($high_water, $high_water, '>');
|
||||
$condition_added = TRUE;
|
||||
}
|
||||
if ($condition_added) {
|
||||
$this->query->condition($conditions);
|
||||
}
|
||||
}
|
||||
|
||||
return new \IteratorIterator($this->query->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Drupal\Core\Database\Query\SelectInterface
|
||||
*/
|
||||
abstract public function query();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
return $this->query()->countQuery()->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can join against the map table.
|
||||
*
|
||||
* This function specifically catches issues when we're migrating with
|
||||
* unique sets of credentials for the source and destination database.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if we can join against the map table otherwise FALSE.
|
||||
*/
|
||||
protected function mapJoinable() {
|
||||
if (!$this->getIds()) {
|
||||
return FALSE;
|
||||
}
|
||||
$id_map = $this->migration->getIdMap();
|
||||
if (!$id_map instanceof Sql) {
|
||||
return FALSE;
|
||||
}
|
||||
$id_map_database_options = $id_map->getDatabase()->getConnectionOptions();
|
||||
$source_database_options = $this->getDatabase()->getConnectionOptions();
|
||||
foreach (array('username', 'password', 'host', 'port', 'namespace', 'driver') as $key) {
|
||||
if (isset($source_database_options[$key])) {
|
||||
if ($id_map_database_options[$key] != $source_database_options[$key]) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
53
core/modules/migrate/src/ProcessPluginBase.php
Normal file
53
core/modules/migrate/src/ProcessPluginBase.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\ProcessPluginBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
|
||||
/**
|
||||
* The base class for all migrate process plugins.
|
||||
*
|
||||
* Migrate process plugins are taking a value and transform them. For example,
|
||||
* transform a human provided name into a machine name, look up an identifier
|
||||
* in a previous migration and so on.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2129651
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
abstract class ProcessPluginBase extends PluginBase implements MigrateProcessInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// Do not call this method from children.
|
||||
if (isset($this->configuration['method'])) {
|
||||
if (method_exists($this, $this->configuration['method'])) {
|
||||
return $this->{$this->configuration['method']}($value, $migrate_executable, $row, $destination_property);
|
||||
}
|
||||
throw new \BadMethodCallException(sprintf('The %s method does not exist in the %s plugin.', $this->configuration['method'], $this->pluginId));
|
||||
}
|
||||
else {
|
||||
throw new \BadMethodCallException(sprintf('The "method" key in the plugin configuration must to be set for the %s plugin.', $this->pluginId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function multiple() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
323
core/modules/migrate/src/Row.php
Normal file
323
core/modules/migrate/src/Row.php
Normal file
|
@ -0,0 +1,323 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Row.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
|
||||
/**
|
||||
* Stores a row.
|
||||
*/
|
||||
class Row {
|
||||
|
||||
/**
|
||||
* The actual values of the source row.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source = array();
|
||||
|
||||
/**
|
||||
* The source identifiers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIds = array();
|
||||
|
||||
/**
|
||||
* The destination values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destination = array();
|
||||
|
||||
/**
|
||||
* Level separator of destination and source properties.
|
||||
*/
|
||||
const PROPERTY_SEPARATOR = '/';
|
||||
|
||||
/**
|
||||
* The mapping between source and destination identifiers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $idMap = array(
|
||||
'original_hash' => '',
|
||||
'hash' => '',
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the source has been frozen already.
|
||||
*
|
||||
* Once frozen the source can not be changed any more.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $frozen = FALSE;
|
||||
|
||||
/**
|
||||
* The raw destination properties.
|
||||
*
|
||||
* Unlike $destination which is set by using
|
||||
* \Drupal\Component\Utility\NestedArray::setValue() this array contains
|
||||
* the destination as setDestinationProperty was called.
|
||||
*
|
||||
* @var array
|
||||
* The raw destination.
|
||||
*
|
||||
* @see getRawDestination()
|
||||
*/
|
||||
protected $rawDestination;
|
||||
|
||||
/**
|
||||
* TRUE when this row is a stub.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isStub = FALSE;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Migrate\Row object.
|
||||
*
|
||||
* @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.
|
||||
* @param bool $is_stub
|
||||
* TRUE if the row being created is a stub.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when a source ID property does not exist.
|
||||
*/
|
||||
public function __construct(array $values, array $source_ids, $is_stub = FALSE) {
|
||||
$this->source = $values;
|
||||
$this->sourceIds = $source_ids;
|
||||
$this->isStub = $is_stub;
|
||||
foreach (array_keys($source_ids) as $id) {
|
||||
if (!$this->hasSourceProperty($id)) {
|
||||
throw new \InvalidArgumentException("$id has no value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the values of the source identifiers.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the values of the source identifiers.
|
||||
*/
|
||||
public function getSourceIdValues() {
|
||||
return array_intersect_key($this->source, $this->sourceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a source has a property.
|
||||
*
|
||||
* @param string $property
|
||||
* A property on the source.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the source has property; FALSE otherwise.
|
||||
*/
|
||||
public function hasSourceProperty($property) {
|
||||
return NestedArray::keyExists($this->source, explode(static::PROPERTY_SEPARATOR, $property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a source property.
|
||||
*
|
||||
* @param string $property
|
||||
* A property on the source.
|
||||
*
|
||||
* @return mixed|null
|
||||
* The found returned property or NULL if not found.
|
||||
*/
|
||||
public function getSourceProperty($property) {
|
||||
$return = NestedArray::getValue($this->source, explode(static::PROPERTY_SEPARATOR, $property), $key_exists);
|
||||
if ($key_exists) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole source array.
|
||||
*
|
||||
* @return array
|
||||
* An array of source plugins.
|
||||
*/
|
||||
public function getSource() {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a source property.
|
||||
*
|
||||
* This can only be called from the source plugin.
|
||||
*
|
||||
* @param string $property
|
||||
* A property on the source.
|
||||
* @param mixed $data
|
||||
* The property value to set on the source.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setSourceProperty($property, $data) {
|
||||
if ($this->frozen) {
|
||||
throw new \Exception("The source is frozen and can't be changed any more");
|
||||
}
|
||||
else {
|
||||
NestedArray::setValue($this->source, explode(static::PROPERTY_SEPARATOR, $property), $data, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Freezes the source.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function freezeSource() {
|
||||
$this->frozen = TRUE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if destination property exists.
|
||||
*
|
||||
* @param array|string $property
|
||||
* An array of properties on the destination.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the destination property exists.
|
||||
*/
|
||||
public function hasDestinationProperty($property) {
|
||||
return NestedArray::keyExists($this->destination, explode(static::PROPERTY_SEPARATOR, $property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets destination properties.
|
||||
*
|
||||
* @param string $property
|
||||
* The name of the destination property.
|
||||
* @param mixed $value
|
||||
* The property value to set on the destination.
|
||||
*/
|
||||
public function setDestinationProperty($property, $value) {
|
||||
$this->rawDestination[$property] = $value;
|
||||
NestedArray::setValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property), $value, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole destination array.
|
||||
*
|
||||
* @return array
|
||||
* An array of destination values.
|
||||
*/
|
||||
public function getDestination() {
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw destination. Rarely necessary.
|
||||
*
|
||||
* For example calling setDestination('foo/bar', 'baz') results in
|
||||
* @code
|
||||
* $this->destination['foo']['bar'] = 'baz';
|
||||
* $this->rawDestination['foo/bar'] = 'baz';
|
||||
* @endcode
|
||||
*
|
||||
* @return array
|
||||
* The raw destination values.
|
||||
*/
|
||||
public function getRawDestination() {
|
||||
return $this->rawDestination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a destination property.
|
||||
*
|
||||
* @param string $property
|
||||
* The name of a property on the destination.
|
||||
*
|
||||
* @return mixed
|
||||
* The destination value.
|
||||
*/
|
||||
public function getDestinationProperty($property) {
|
||||
return NestedArray::getValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Migrate ID mappings.
|
||||
*
|
||||
* @param array $id_map
|
||||
* An array of mappings between source ID and destination ID.
|
||||
*/
|
||||
public function setIdMap(array $id_map) {
|
||||
$this->idMap = $id_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Migrate ID mappings.
|
||||
*
|
||||
* @return array
|
||||
* An array of mapping between source and destination identifiers.
|
||||
*/
|
||||
public function getIdMap() {
|
||||
return $this->idMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the hash for the row.
|
||||
*/
|
||||
public function rehash() {
|
||||
$this->idMap['original_hash'] = $this->idMap['hash'];
|
||||
$this->idMap['hash'] = hash('sha256', serialize($this->source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the row has changed compared to the original ID map.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the row has changed, FALSE otherwise. If setIdMap() was not
|
||||
* called, this always returns FALSE.
|
||||
*/
|
||||
public function changed() {
|
||||
return $this->idMap['original_hash'] != $this->idMap['hash'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this row needs an update.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the row needs updating, FALSE otherwise.
|
||||
*/
|
||||
public function needsUpdate() {
|
||||
return $this->idMap['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash for the source values..
|
||||
*
|
||||
* @return mixed
|
||||
* The hash of the source values.
|
||||
*/
|
||||
public function getHash() {
|
||||
return $this->idMap['hash'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether this row is a stub.
|
||||
*
|
||||
* @return bool
|
||||
* The current stub value.
|
||||
*/
|
||||
public function isStub() {
|
||||
return $this->isStub;
|
||||
}
|
||||
}
|
295
core/modules/migrate/src/Tests/EntityFileTest.php
Normal file
295
core/modules/migrate/src/Tests/EntityFileTest.php
Normal file
|
@ -0,0 +1,295 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Tests\EntityFileTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Tests;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityFile;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the entity file destination plugin.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class EntityFileTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'entity_test', 'user', 'file');
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Tests\TestEntityFile $destination
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->destination = new TestEntityFile([]);
|
||||
$this->destination->streamWrapperManager = \Drupal::getContainer()->get('stream_wrapper_manager');
|
||||
$this->destination->fileSystem = \Drupal::getContainer()->get('file_system');
|
||||
$this->installEntitySchema('file');
|
||||
|
||||
file_put_contents('/tmp/test-file.jpg', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful imports/copies.
|
||||
*/
|
||||
public function testSuccessfulCopies() {
|
||||
foreach ($this->localFileDataProvider() as $data) {
|
||||
list($row_values, $destination_path, $expected, $source_base_path) = $data;
|
||||
|
||||
$this->doImport($row_values, $destination_path, $source_base_path);
|
||||
$message = $expected ? sprintf('File %s exists', $destination_path) : sprintf('File %s does not exist', $destination_path);
|
||||
$this->assertIdentical($expected, is_file($destination_path), $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The data provider for testing the file destination.
|
||||
*
|
||||
* @return array
|
||||
* An array of file permutations to test.
|
||||
*/
|
||||
protected function localFileDataProvider() {
|
||||
global $base_url;
|
||||
return [
|
||||
// Test a local to local copy.
|
||||
[['filepath' => 'core/modules/simpletest/files/image-test.jpg'], 'public://file1.jpg', TRUE, DRUPAL_ROOT . '/'],
|
||||
// Test a temporary file using an absolute path.
|
||||
[['filepath' => '/tmp/test-file.jpg'], 'temporary://test.jpg', TRUE, ''],
|
||||
// Test a temporary file using a relative path.
|
||||
[['filepath' => 'test-file.jpg'], 'temporary://core/modules/simpletest/files/test.jpg', TRUE, '/tmp/'],
|
||||
// Test a remote path to local.
|
||||
[['filepath' => 'core/modules/simpletest/files/image-test.jpg'], 'public://remote-file.jpg', TRUE, $base_url . '/'],
|
||||
// Test a remote path to local inside a folder that doesn't exist.
|
||||
[['filepath' => 'core/modules/simpletest/files/image-test.jpg'], 'public://folder/remote-file.jpg', TRUE, DRUPAL_ROOT . '/'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that non-existent files throw an exception.
|
||||
*/
|
||||
public function testNonExistentSourceFile() {
|
||||
$destination = '/non/existent/file';
|
||||
try {
|
||||
// If this test passes, doImport() will raise a MigrateException and
|
||||
// we'll never reach fail().
|
||||
$this->doImport(['filepath' => $destination], 'public://wontmatter.jpg');
|
||||
$this->fail('Expected Drupal\migrate\MigrateException when importing ' . $destination);
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->assertIdentical($e->getMessage(), 'File ' . $destination . ' does not exist.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests various invocations of the writeFile() method.
|
||||
*/
|
||||
public function testWriteFile() {
|
||||
$plugin = $this->destination;
|
||||
$method = new \ReflectionMethod($plugin, 'writeFile');
|
||||
$method->setAccessible(TRUE);
|
||||
|
||||
touch('temporary://baz.txt');
|
||||
|
||||
// Moving an actual file should return TRUE.
|
||||
$plugin->configuration['move'] = TRUE;
|
||||
$this->assertTrue($method->invoke($plugin, 'temporary://baz.txt', 'public://foo.txt'));
|
||||
|
||||
// Trying to move a non-existent file should return FALSE.
|
||||
$this->assertFalse($method->invoke($plugin, 'temporary://invalid.txt', 'public://invalid.txt'));
|
||||
|
||||
// Copying over a file that already exists should replace the existing file.
|
||||
$plugin->configuration['move'] = FALSE;
|
||||
touch('temporary://baz.txt');
|
||||
$this->assertTrue($method->invoke($plugin, 'temporary://baz.txt', 'public://foo.txt'));
|
||||
// Copying over a file that already exists should rename the resulting file
|
||||
// if FILE_EXISTS_RENAME is specified.
|
||||
$method->invoke($plugin, 'temporary://baz.txt', 'public://foo.txt', FILE_EXISTS_RENAME);
|
||||
$this->assertTrue(file_exists('public://foo_0.txt'));
|
||||
|
||||
// Trying to copy a non-existent file should return FALSE.
|
||||
$this->assertFalse($method->invoke($plugin, 'temporary://invalid.txt', 'public://invalid.txt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests various invocations of the getOverwriteMode() method.
|
||||
*/
|
||||
public function testGetOverwriteMode() {
|
||||
$plugin = $this->destination;
|
||||
$method = new \ReflectionMethod($plugin, 'getOverwriteMode');
|
||||
$method->setAccessible(TRUE);
|
||||
|
||||
$row = new Row([], []);
|
||||
// If the plugin is not configured to rename the destination file, we should
|
||||
// always get FILE_EXISTS_REPLACE.
|
||||
$this->assertIdentical(FILE_EXISTS_REPLACE, $method->invoke($plugin, $row));
|
||||
|
||||
// When the plugin IS configured to rename the destination file, it should
|
||||
// return FILE_EXISTS_RENAME if the destination entity already exists,
|
||||
// and FILE_EXISTS_REPLACE otherwise.
|
||||
$plugin->configuration['rename'] = TRUE;
|
||||
$plugin->storage = \Drupal::entityManager()->getStorage('file');
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
$file = $plugin->storage->create();
|
||||
touch('public://foo.txt');
|
||||
$file->setFileUri('public://foo.txt');
|
||||
$file->save();
|
||||
$row->setDestinationProperty($plugin->storage->getEntityType()->getKey('id'), $file->id());
|
||||
$this->assertIdentical(FILE_EXISTS_RENAME, $method->invoke($plugin, $row));
|
||||
unlink('public://foo.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests various invocations of the getDirectory() method.
|
||||
*/
|
||||
public function testGetDirectory() {
|
||||
$plugin = $this->destination;
|
||||
$method = new \ReflectionMethod($plugin, 'getDirectory');
|
||||
$method->setAccessible(TRUE);
|
||||
|
||||
$this->assertEqual('public://foo', $method->invoke($plugin, 'public://foo/baz.txt'));
|
||||
$this->assertEqual('/path/to', $method->invoke($plugin, '/path/to/foo.txt'));
|
||||
// A directory like public:// (no path) needs to resolve to a physical path.
|
||||
$fs = \Drupal::getContainer()->get('file_system');
|
||||
$this->assertEqual($fs->realpath(Settings::get('file_public_path')), $method->invoke($plugin, 'public://foo.txt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests various invocations of the isLocationUnchanged() method.
|
||||
*/
|
||||
public function testIsLocationUnchanged() {
|
||||
$plugin = $this->destination;
|
||||
$method = new \ReflectionMethod($plugin, 'isLocationUnchanged');
|
||||
$method->setAccessible(TRUE);
|
||||
|
||||
$public_dir = Settings::get('file_public_path');
|
||||
|
||||
// Due to the limitations of realpath(), the source file must exist.
|
||||
touch('public://foo.txt');
|
||||
$this->assertTrue($method->invoke($plugin, $public_dir . '/foo.txt', 'public://foo.txt'));
|
||||
unlink('public://foo.txt');
|
||||
|
||||
$temporary_file = '/tmp/foo.txt';
|
||||
touch($temporary_file);
|
||||
$this->assertTrue($method->invoke($plugin, $temporary_file, 'temporary://foo.txt'));
|
||||
unlink($temporary_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests various invocations of the isLocalUri() method.
|
||||
*/
|
||||
public function testIsLocalUri() {
|
||||
$plugin = $this->destination;
|
||||
$method = new \ReflectionMethod($plugin, 'isLocalUri');
|
||||
$method->setAccessible(TRUE);
|
||||
|
||||
$this->assertTrue($method->invoke($plugin, 'public://foo.txt'));
|
||||
$this->assertTrue($method->invoke($plugin, 'public://path/to/foo.txt'));
|
||||
$this->assertTrue($method->invoke($plugin, 'temporary://foo.txt'));
|
||||
$this->assertTrue($method->invoke($plugin, 'temporary://path/to/foo.txt'));
|
||||
$this->assertTrue($method->invoke($plugin, 'foo.txt'));
|
||||
$this->assertTrue($method->invoke($plugin, '/path/to/files/foo.txt'));
|
||||
$this->assertTrue($method->invoke($plugin, 'relative/path/to/foo.txt'));
|
||||
$this->assertFalse($method->invoke($plugin, 'http://www.example.com/foo.txt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Do an import using the destination.
|
||||
*
|
||||
* @param array $row_values
|
||||
* An array of row values.
|
||||
* @param string $destination_path
|
||||
* The destination path to copy to.
|
||||
* @param string $source_base_path
|
||||
* The source base path.
|
||||
* @return array
|
||||
* An array of saved entities ids.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
protected function doImport($row_values, $destination_path, $source_base_path = '') {
|
||||
$row = new Row($row_values, []);
|
||||
$row->setDestinationProperty('uri', $destination_path);
|
||||
$this->destination->configuration['source_base_path'] = $source_base_path;
|
||||
|
||||
// Importing asserts there are no errors, then we just check the file has
|
||||
// been copied into place.
|
||||
return $this->destination->import($row, array());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestEntityFile extends EntityFile {
|
||||
|
||||
/**
|
||||
* This is needed to be passed to $this->save().
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface
|
||||
*/
|
||||
public $mockEntity;
|
||||
|
||||
/**
|
||||
* Make this public for easy writing during tests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $configuration;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
public $storage;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
|
||||
*/
|
||||
public $streamWrapperManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
public $fileSystem;
|
||||
|
||||
public function __construct($configuration) {
|
||||
$configuration += array(
|
||||
'source_base_path' => '',
|
||||
'source_path_property' => 'filepath',
|
||||
'destination_path_property' => 'uri',
|
||||
'move' => FALSE,
|
||||
'urlencode' => FALSE,
|
||||
);
|
||||
$this->configuration = $configuration;
|
||||
// We need a mock entity to be passed to save to prevent strict exceptions.
|
||||
$this->mockEntity = EntityTest::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
return $this->mockEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {}
|
||||
|
||||
}
|
28
core/modules/migrate/src/Tests/MigrateDumpAlterInterface.php
Normal file
28
core/modules/migrate/src/Tests/MigrateDumpAlterInterface.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Tests\MigrateDumpAlterInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Tests;
|
||||
|
||||
use Drupal\simpletest\TestBase;
|
||||
|
||||
/**
|
||||
* Allows tests to alter dumps after they've loaded.
|
||||
*
|
||||
* @s
|
||||
* @see \Drupal\migrate_drupal\Tests\d6\MigrateFileTest
|
||||
*/
|
||||
interface MigrateDumpAlterInterface {
|
||||
|
||||
/**
|
||||
* Allows tests to alter dumps after they've loaded.
|
||||
*
|
||||
* @param \Drupal\simpletest\TestBase $test
|
||||
* The test that is being run.
|
||||
*/
|
||||
public static function migrateDumpAlter(TestBase $test);
|
||||
|
||||
}
|
167
core/modules/migrate/src/Tests/MigrateTestBase.php
Normal file
167
core/modules/migrate/src/Tests/MigrateTestBase.php
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Tests\MigrateTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Tests;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Base class for migration tests.
|
||||
*/
|
||||
abstract class MigrateTestBase extends KernelTestBase implements MigrateMessageInterface {
|
||||
|
||||
/**
|
||||
* The file path(s) to the dumped database(s) to load into the child site.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $databaseDumpFiles = array();
|
||||
|
||||
|
||||
/**
|
||||
* TRUE to collect messages instead of displaying them.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $collectMessages = FALSE;
|
||||
|
||||
/**
|
||||
* A two dimensional array of messages.
|
||||
*
|
||||
* The first key is the type of message, the second is just numeric. Values
|
||||
* are the messages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migrateMessages;
|
||||
|
||||
public static $modules = array('migrate');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
foreach ($connection_info as $target => $value) {
|
||||
$prefix = is_array($value['prefix']) ? $value['prefix']['default'] : $value['prefix'];
|
||||
// Simpletest uses 7 character prefixes at most so this can't cause
|
||||
// collisions.
|
||||
$connection_info[$target]['prefix']['default'] = $prefix . '0';
|
||||
|
||||
// Add the original simpletest prefix so SQLite can attach its database.
|
||||
// @see \Drupal\Core\Database\Driver\sqlite\Connection::init()
|
||||
$connection_info[$target]['prefix'][$value['prefix']['default']] = $value['prefix']['default'];
|
||||
}
|
||||
Database::addConnectionInfo('migrate', 'default', $connection_info['default']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tearDown() {
|
||||
Database::removeConnection('migrate');
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration object.
|
||||
* @param array $files
|
||||
* An array of files.
|
||||
*/
|
||||
protected function prepare(MigrationInterface $migration, array $files = array()) {
|
||||
$this->loadDumps($files);
|
||||
if ($this instanceof MigrateDumpAlterInterface) {
|
||||
static::migrateDumpAlter($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Drupal 6 database dumps to be used.
|
||||
*
|
||||
* @param array $files
|
||||
* An array of files.
|
||||
* @param string $method
|
||||
* The name of the method in the dump class to use. Defaults to load.
|
||||
*/
|
||||
protected function loadDumps($files, $method = 'load') {
|
||||
// Load the database from the portable PHP dump.
|
||||
// The files may be gzipped.
|
||||
foreach ($files as $file) {
|
||||
if (substr($file, -3) == '.gz') {
|
||||
$file = "compress.zlib://$file";
|
||||
require $file;
|
||||
}
|
||||
preg_match('/^namespace (.*);$/m', file_get_contents($file), $matches);
|
||||
$class = $matches[1] . '\\' . basename($file, '.php');
|
||||
(new $class(Database::getConnection('default', 'migrate')))->$method();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare any dependent migrations.
|
||||
*
|
||||
* @param array $id_mappings
|
||||
* A list of id mappings keyed by migration ids. Each id mapping is a list
|
||||
* of two arrays, the first are source ids and the second are destination
|
||||
* ids.
|
||||
*/
|
||||
protected function prepareMigrations(array $id_mappings) {
|
||||
/** @var \Drupal\migrate\Entity\MigrationInterface[] $migrations */
|
||||
$migrations = entity_load_multiple('migration', array_keys($id_mappings));
|
||||
foreach ($id_mappings as $migration_id => $data) {
|
||||
$migration = $migrations[$migration_id];
|
||||
|
||||
// Mark the dependent migrations as complete.
|
||||
$migration->setMigrationResult(MigrationInterface::RESULT_COMPLETED);
|
||||
|
||||
$id_map = $migration->getIdMap();
|
||||
$id_map->setMessage($this);
|
||||
$source_ids = $migration->getSourcePlugin()->getIds();
|
||||
foreach ($data as $id_mapping) {
|
||||
$row = new Row(array_combine(array_keys($source_ids), $id_mapping[0]), $source_ids);
|
||||
$id_map->saveIdMapping($row, $id_mapping[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function display($message, $type = 'status') {
|
||||
if ($this->collectMessages) {
|
||||
$this->migrateMessages[$type][] = $message;
|
||||
}
|
||||
else {
|
||||
$this->assert($type == 'status', $message, 'migrate');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start collecting messages and erase previous messages.
|
||||
*/
|
||||
public function startCollectingMessages() {
|
||||
$this->collectMessages = TRUE;
|
||||
$this->migrateMessages = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop collecting messages.
|
||||
*/
|
||||
public function stopCollectingMessages() {
|
||||
$this->collectMessages = FALSE;
|
||||
}
|
||||
|
||||
}
|
106
core/modules/migrate/src/Tests/SqlBaseTest.php
Normal file
106
core/modules/migrate/src/Tests/SqlBaseTest.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate\Tests\SqlBaseTest
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate\Tests;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\source\TestSqlBase;
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* Test the functionality of SqlBase.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class SqlBaseTest extends MigrateTestBase {
|
||||
|
||||
/**
|
||||
* Test different connection types.
|
||||
*/
|
||||
public function testConnectionTypes() {
|
||||
$sql_base = new TestSqlBase();
|
||||
|
||||
// Check the default values.
|
||||
$this->assertIdentical($sql_base->getDatabase()->getTarget(), 'default');
|
||||
$this->assertIdentical($sql_base->getDatabase()->getKey(), 'migrate');
|
||||
|
||||
$target = 'test_db_target';
|
||||
$key = 'test_migrate_connection';
|
||||
$config = array('target' => $target, 'key' => $key);
|
||||
$sql_base->setConfiguration($config);
|
||||
Database::addConnectionInfo($key, $target, Database::getConnectionInfo('default')['default']);
|
||||
|
||||
// Validate we've injected our custom key and target.
|
||||
$this->assertIdentical($sql_base->getDatabase()->getTarget(), $target);
|
||||
$this->assertIdentical($sql_base->getDatabase()->getKey(), $key);
|
||||
|
||||
// Now test we can have SqlBase create the connection from an info array.
|
||||
$sql_base = new TestSqlBase();
|
||||
|
||||
$target = 'test_db_target2';
|
||||
$key = 'test_migrate_connection2';
|
||||
$database = Database::getConnectionInfo('default')['default'];
|
||||
$config = array('target' => $target, 'key' => $key, 'database' => $database);
|
||||
$sql_base->setConfiguration($config);
|
||||
|
||||
// Call getDatabase() to get the connection defined.
|
||||
$sql_base->getDatabase();
|
||||
|
||||
// Validate the connection has been created with the right values.
|
||||
$this->assertIdentical(Database::getConnectionInfo($key)[$target], $database);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
/**
|
||||
* A dummy source to help with testing SqlBase.
|
||||
*
|
||||
* @package Drupal\migrate\Plugin\migrate\source
|
||||
*/
|
||||
class TestSqlBase extends SqlBase {
|
||||
|
||||
/**
|
||||
* Override the constructor so we can create one easily.
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Get the database without caching it.
|
||||
*/
|
||||
public function getDatabase() {
|
||||
$this->database = NULL;
|
||||
return parent::getDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow us to set the configuration from a test.
|
||||
*
|
||||
* @param array $config
|
||||
* The config array.
|
||||
*/
|
||||
public function setConfiguration($config) {
|
||||
$this->configuration = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {}
|
||||
|
||||
}
|
31
core/modules/migrate/tests/src/Unit/Entity/MigrationTest.php
Normal file
31
core/modules/migrate/tests/src/Unit/Entity/MigrationTest.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\Entity\MigrationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\Entity;
|
||||
|
||||
use Drupal\migrate\Entity\Migration;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Tests the migrate entity.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\migrate\Entity\Migration
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrationTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests Migration::getProcessPlugins()
|
||||
*
|
||||
* @covers ::getProcessPlugins
|
||||
*/
|
||||
public function testGetProcessPlugins() {
|
||||
$migration = new Migration([], 'migration');
|
||||
$this->assertEquals([], $migration->getProcessPlugins([]));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrateExecutableMemoryExceededTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
/**
|
||||
* Tests the \Drupal\migrate\MigrateExecutable::memoryExceeded() method.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateExecutableMemoryExceededTest extends MigrateTestCase {
|
||||
|
||||
/**
|
||||
* The mocked migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Entity\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The mocked migrate message.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* The tested migrate executable.
|
||||
*
|
||||
* @var \Drupal\Tests\migrate\Unit\TestMigrateExecutable
|
||||
*/
|
||||
protected $executable;
|
||||
|
||||
/**
|
||||
* The migration configuration, initialized to set the ID to test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migrationConfiguration = array(
|
||||
'id' => 'test',
|
||||
);
|
||||
|
||||
/**
|
||||
* php.init memory_limit value.
|
||||
*/
|
||||
protected $memoryLimit = 10000000;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->migration = $this->getMigration();
|
||||
$this->message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
|
||||
|
||||
$this->executable = new TestMigrateExecutable($this->migration, $this->message);
|
||||
$this->executable->setStringTranslation($this->getStringTranslationStub());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the actual test.
|
||||
*
|
||||
* @param string $message
|
||||
* The second message to assert.
|
||||
* @param bool $memory_exceeded
|
||||
* Whether to test the memory exceeded case.
|
||||
* @param int $memory_usage_first
|
||||
* (optional) The first memory usage value.
|
||||
* @param int $memory_usage_second
|
||||
* (optional) The fake amount of memory usage reported after memory reclaim.
|
||||
* @param int $memory_limit
|
||||
* (optional) The memory limit.
|
||||
*/
|
||||
protected function runMemoryExceededTest($message, $memory_exceeded, $memory_usage_first = NULL, $memory_usage_second = NULL, $memory_limit = NULL) {
|
||||
$this->executable->setMemoryLimit($memory_limit ?: $this->memoryLimit);
|
||||
$this->executable->setMemoryUsage($memory_usage_first ?: $this->memoryLimit, $memory_usage_second ?: $this->memoryLimit);
|
||||
$this->executable->setMemoryThreshold(0.85);
|
||||
if ($message) {
|
||||
$this->executable->message->expects($this->at(0))
|
||||
->method('display')
|
||||
->with($this->stringContains('reclaiming memory'));
|
||||
$this->executable->message->expects($this->at(1))
|
||||
->method('display')
|
||||
->with($this->stringContains($message));
|
||||
}
|
||||
else {
|
||||
$this->executable->message->expects($this->never())
|
||||
->method($this->anything());
|
||||
}
|
||||
$result = $this->executable->memoryExceeded();
|
||||
$this->assertEquals($memory_exceeded, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests memoryExceeded method when a new batch is needed.
|
||||
*/
|
||||
public function testMemoryExceededNewBatch() {
|
||||
// First case try reset and then start new batch.
|
||||
$this->runMemoryExceededTest('starting new batch', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests memoryExceeded method when enough is cleared.
|
||||
*/
|
||||
public function testMemoryExceededClearedEnough() {
|
||||
$this->runMemoryExceededTest('reclaimed enough', FALSE, $this->memoryLimit, $this->memoryLimit * 0.75);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests memoryExceeded when memory usage is not exceeded.
|
||||
*/
|
||||
public function testMemoryNotExceeded() {
|
||||
$this->runMemoryExceededTest('', FALSE, floor($this->memoryLimit * 0.85) - 1);
|
||||
}
|
||||
|
||||
}
|
525
core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
Normal file
525
core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
Normal file
|
@ -0,0 +1,525 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrateExecutableTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Tests\migrate\Unit\MigrateExecutableTest
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateExecutableTest extends MigrateTestCase {
|
||||
|
||||
/**
|
||||
* The mocked migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Entity\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The mocked migrate message.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* The tested migrate executable.
|
||||
*
|
||||
* @var \Drupal\Tests\migrate\Unit\TestMigrateExecutable
|
||||
*/
|
||||
protected $executable;
|
||||
|
||||
protected $migrationConfiguration = array(
|
||||
'id' => 'test',
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->migration = $this->getMigration();
|
||||
$this->message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
|
||||
|
||||
$this->executable = new TestMigrateExecutable($this->migration, $this->message);
|
||||
$this->executable->setStringTranslation($this->getStringTranslationStub());
|
||||
$this->executable->setTimeThreshold(0.1);
|
||||
$this->executable->limit = array('unit' => 'second', 'value' => 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an import with an incomplete rewinding.
|
||||
*/
|
||||
public function testImportWithFailingRewind() {
|
||||
$exception_message = $this->getRandomGenerator()->string();
|
||||
$source = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
|
||||
$source->expects($this->once())
|
||||
->method('rewind')
|
||||
->will($this->throwException(new \Exception($exception_message)));
|
||||
|
||||
$this->migration->expects($this->any())
|
||||
->method('getSourcePlugin')
|
||||
->will($this->returnValue($source));
|
||||
|
||||
// Ensure that a message with the proper message was added.
|
||||
$this->message->expects($this->once())
|
||||
->method('display')
|
||||
->with("Migration failed with source plugin exception: $exception_message");
|
||||
|
||||
$result = $this->executable->import();
|
||||
$this->assertEquals(MigrationInterface::RESULT_FAILED, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the import method with a valid row.
|
||||
*/
|
||||
public function testImportWithValidRow() {
|
||||
$source = $this->getMockSource();
|
||||
|
||||
$row = $this->getMockBuilder('Drupal\migrate\Row')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$row->expects($this->once())
|
||||
->method('getSourceIdValues')
|
||||
->will($this->returnValue(array('id' => 'test')));
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('lookupDestinationId')
|
||||
->with(array('id' => 'test'))
|
||||
->will($this->returnValue(array('test')));
|
||||
|
||||
$source->expects($this->once())
|
||||
->method('current')
|
||||
->will($this->returnValue($row));
|
||||
|
||||
$this->executable->setSource($source);
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getProcessPlugins')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
|
||||
$destination->expects($this->once())
|
||||
->method('import')
|
||||
->with($row, array('test'))
|
||||
->will($this->returnValue(array('id' => 'test')));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
|
||||
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
|
||||
|
||||
$this->assertSame(1, $this->executable->getSuccessesSinceFeedback());
|
||||
$this->assertSame(1, $this->executable->getTotalSuccesses());
|
||||
$this->assertSame(1, $this->executable->getTotalProcessed());
|
||||
$this->assertSame(1, $this->executable->getProcessedSinceFeedback());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the import method with a valid row.
|
||||
*/
|
||||
public function testImportWithValidRowWithoutDestinationId() {
|
||||
$source = $this->getMockSource();
|
||||
|
||||
$row = $this->getMockBuilder('Drupal\migrate\Row')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$row->expects($this->once())
|
||||
->method('getSourceIdValues')
|
||||
->will($this->returnValue(array('id' => 'test')));
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('lookupDestinationId')
|
||||
->with(array('id' => 'test'))
|
||||
->will($this->returnValue(array('test')));
|
||||
|
||||
$source->expects($this->once())
|
||||
->method('current')
|
||||
->will($this->returnValue($row));
|
||||
|
||||
$this->executable->setSource($source);
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getProcessPlugins')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
|
||||
$destination->expects($this->once())
|
||||
->method('import')
|
||||
->with($row, array('test'))
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
|
||||
$this->idMap->expects($this->never())
|
||||
->method('saveIdMapping');
|
||||
|
||||
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
|
||||
|
||||
$this->assertSame(1, $this->executable->getSuccessesSinceFeedback());
|
||||
$this->assertSame(1, $this->executable->getTotalSuccesses());
|
||||
$this->assertSame(1, $this->executable->getTotalProcessed());
|
||||
$this->assertSame(1, $this->executable->getProcessedSinceFeedback());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the import method with a valid row.
|
||||
*/
|
||||
public function testImportWithValidRowNoDestinationValues() {
|
||||
$source = $this->getMockSource();
|
||||
|
||||
$row = $this->getMockBuilder('Drupal\migrate\Row')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$row->expects($this->once())
|
||||
->method('getSourceIdValues')
|
||||
->will($this->returnValue(array('id' => 'test')));
|
||||
|
||||
$source->expects($this->once())
|
||||
->method('current')
|
||||
->will($this->returnValue($row));
|
||||
|
||||
$this->executable->setSource($source);
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getProcessPlugins')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
|
||||
$destination->expects($this->once())
|
||||
->method('import')
|
||||
->with($row, array('test'))
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('delete')
|
||||
->with(array('id' => 'test'), TRUE);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
->with($row, array(), MigrateIdMapInterface::STATUS_FAILED, NULL);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('messageCount')
|
||||
->will($this->returnValue(0));
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveMessage');
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('lookupDestinationId')
|
||||
->with(array('id' => 'test'))
|
||||
->will($this->returnValue(array('test')));
|
||||
|
||||
$this->message->expects($this->once())
|
||||
->method('display')
|
||||
->with('New object was not saved, no error provided');
|
||||
|
||||
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the import method with a MigrateException being thrown.
|
||||
*/
|
||||
public function testImportWithValidRowWithMigrateException() {
|
||||
$exception_message = $this->getRandomGenerator()->string();
|
||||
$source = $this->getMockSource();
|
||||
|
||||
$row = $this->getMockBuilder('Drupal\migrate\Row')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$row->expects($this->once())
|
||||
->method('getSourceIdValues')
|
||||
->will($this->returnValue(array('id' => 'test')));
|
||||
|
||||
$source->expects($this->once())
|
||||
->method('current')
|
||||
->will($this->returnValue($row));
|
||||
|
||||
$this->executable->setSource($source);
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getProcessPlugins')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
|
||||
$destination->expects($this->once())
|
||||
->method('import')
|
||||
->with($row, array('test'))
|
||||
->will($this->throwException(new MigrateException($exception_message)));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
->with($row, array(), MigrateIdMapInterface::STATUS_FAILED, NULL);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveMessage');
|
||||
|
||||
$this->message->expects($this->once())
|
||||
->method('display')
|
||||
->with($exception_message);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('lookupDestinationId')
|
||||
->with(array('id' => 'test'))
|
||||
->will($this->returnValue(array('test')));
|
||||
|
||||
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the import method with a regular Exception being thrown.
|
||||
*/
|
||||
public function testImportWithValidRowWithException() {
|
||||
$exception_message = $this->getRandomGenerator()->string();
|
||||
$source = $this->getMockSource();
|
||||
|
||||
$row = $this->getMockBuilder('Drupal\migrate\Row')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$row->expects($this->once())
|
||||
->method('getSourceIdValues')
|
||||
->will($this->returnValue(array('id' => 'test')));
|
||||
|
||||
$source->expects($this->once())
|
||||
->method('current')
|
||||
->will($this->returnValue($row));
|
||||
|
||||
$this->executable->setSource($source);
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getProcessPlugins')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
|
||||
$destination->expects($this->once())
|
||||
->method('import')
|
||||
->with($row, array('test'))
|
||||
->will($this->throwException(new \Exception($exception_message)));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
->with($row, array(), MigrateIdMapInterface::STATUS_FAILED, NULL);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveMessage');
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('lookupDestinationId')
|
||||
->with(array('id' => 'test'))
|
||||
->will($this->returnValue(array('test')));
|
||||
|
||||
$this->message->expects($this->once())
|
||||
->method('display')
|
||||
->with($exception_message);
|
||||
|
||||
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests time limit option method.
|
||||
*/
|
||||
public function testTimeOptionExceeded() {
|
||||
// Assert time limit of one second (test configuration default) is exceeded.
|
||||
$this->executable->setTimeElapsed(1);
|
||||
$this->assertTrue($this->executable->timeOptionExceeded());
|
||||
// Assert time limit not exceeded.
|
||||
$this->executable->limit = array('unit' => 'seconds', 'value' => (int) $_SERVER['REQUEST_TIME'] - 3600);
|
||||
$this->assertFalse($this->executable->timeOptionExceeded());
|
||||
// Assert no time limit.
|
||||
$this->executable->limit = array();
|
||||
$this->assertFalse($this->executable->timeOptionExceeded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests get time limit method.
|
||||
*/
|
||||
public function testGetTimeLimit() {
|
||||
// Assert time limit has a unit of one second (test configuration default).
|
||||
$limit = $this->executable->limit;
|
||||
$this->assertArrayHasKey('unit', $limit);
|
||||
$this->assertSame('second', $limit['unit']);
|
||||
$this->assertSame($limit['value'], $this->executable->getTimeLimit());
|
||||
// Assert time limit has a unit of multiple seconds.
|
||||
$this->executable->limit = array('unit' => 'seconds', 'value' => 30);
|
||||
$limit = $this->executable->limit;
|
||||
$this->assertArrayHasKey('unit', $limit);
|
||||
$this->assertSame('seconds', $limit['unit']);
|
||||
$this->assertSame($limit['value'], $this->executable->getTimeLimit());
|
||||
// Assert no time limit.
|
||||
$this->executable->limit = array();
|
||||
$limit = $this->executable->limit;
|
||||
$this->assertArrayNotHasKey('unit', $limit);
|
||||
$this->assertArrayNotHasKey('value', $limit);
|
||||
$this->assertNull($this->executable->getTimeLimit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests saving of queued messages.
|
||||
*/
|
||||
public function testSaveQueuedMessages() {
|
||||
// Assert no queued messages before save.
|
||||
$this->assertAttributeEquals(array(), 'queuedMessages', $this->executable);
|
||||
// Set required source_id_values for MigrateIdMapInterface::saveMessage().
|
||||
$expected_messages[] = array('message' => 'message 1', 'level' => MigrationInterface::MESSAGE_ERROR);
|
||||
$expected_messages[] = array('message' => 'message 2', 'level' => MigrationInterface::MESSAGE_WARNING);
|
||||
$expected_messages[] = array('message' => 'message 3', 'level' => MigrationInterface::MESSAGE_INFORMATIONAL);
|
||||
foreach ($expected_messages as $queued_message) {
|
||||
$this->executable->queueMessage($queued_message['message'], $queued_message['level']);
|
||||
}
|
||||
$this->executable->setSourceIdValues(array());
|
||||
$this->assertAttributeEquals($expected_messages, 'queuedMessages', $this->executable);
|
||||
// No asserts of saved messages since coverage exists
|
||||
// in MigrateSqlIdMapTest::saveMessage().
|
||||
$this->executable->saveQueuedMessages();
|
||||
// Assert no queued messages after save.
|
||||
$this->assertAttributeEquals(array(), 'queuedMessages', $this->executable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the queuing of messages.
|
||||
*/
|
||||
public function testQueueMessage() {
|
||||
// Assert no queued messages.
|
||||
$expected_messages = array();
|
||||
$this->assertAttributeEquals(array(), 'queuedMessages', $this->executable);
|
||||
// Assert a single (default level) queued message.
|
||||
$expected_messages[] = array(
|
||||
'message' => 'message 1',
|
||||
'level' => MigrationInterface::MESSAGE_ERROR,
|
||||
);
|
||||
$this->executable->queueMessage('message 1');
|
||||
$this->assertAttributeEquals($expected_messages, 'queuedMessages', $this->executable);
|
||||
// Assert multiple queued messages.
|
||||
$expected_messages[] = array(
|
||||
'message' => 'message 2',
|
||||
'level' => MigrationInterface::MESSAGE_WARNING,
|
||||
);
|
||||
$this->executable->queueMessage('message 2', MigrationInterface::MESSAGE_WARNING);
|
||||
$this->assertAttributeEquals($expected_messages, 'queuedMessages', $this->executable);
|
||||
$expected_messages[] = array(
|
||||
'message' => 'message 3',
|
||||
'level' => MigrationInterface::MESSAGE_INFORMATIONAL,
|
||||
);
|
||||
$this->executable->queueMessage('message 3', MigrationInterface::MESSAGE_INFORMATIONAL);
|
||||
$this->assertAttributeEquals($expected_messages, 'queuedMessages', $this->executable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests maximum execution time (max_execution_time) of an import.
|
||||
*/
|
||||
public function testMaxExecTimeExceeded() {
|
||||
// Assert no max_execution_time value.
|
||||
$this->executable->setMaxExecTime(0);
|
||||
$this->assertFalse($this->executable->maxExecTimeExceeded());
|
||||
// Assert default max_execution_time value does not exceed.
|
||||
$this->executable->setMaxExecTime(30);
|
||||
$this->assertFalse($this->executable->maxExecTimeExceeded());
|
||||
// Assert max_execution_time value is exceeded.
|
||||
$this->executable->setMaxExecTime(1);
|
||||
$this->executable->setTimeElapsed(2);
|
||||
$this->assertTrue($this->executable->maxExecTimeExceeded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the processRow method.
|
||||
*/
|
||||
public function testProcessRow() {
|
||||
$expected = array(
|
||||
'test' => 'test destination',
|
||||
'test1' => 'test1 destination'
|
||||
);
|
||||
foreach ($expected as $key => $value) {
|
||||
$plugins[$key][0] = $this->getMock('Drupal\migrate\Plugin\MigrateProcessInterface');
|
||||
$plugins[$key][0]->expects($this->once())
|
||||
->method('getPluginDefinition')
|
||||
->will($this->returnValue(array()));
|
||||
$plugins[$key][0]->expects($this->once())
|
||||
->method('transform')
|
||||
->will($this->returnValue($value));
|
||||
}
|
||||
$this->migration->expects($this->once())
|
||||
->method('getProcessPlugins')
|
||||
->with(NULL)
|
||||
->will($this->returnValue($plugins));
|
||||
$row = new Row(array(), array());
|
||||
$this->executable->processRow($row);
|
||||
foreach ($expected as $key => $value) {
|
||||
$this->assertSame($row->getDestinationProperty($key), $value);
|
||||
}
|
||||
$this->assertSame(count($row->getDestination()), count($expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the processRow method with an empty pipeline.
|
||||
*/
|
||||
public function testProcessRowEmptyPipeline() {
|
||||
$this->migration->expects($this->once())
|
||||
->method('getProcessPlugins')
|
||||
->with(NULL)
|
||||
->will($this->returnValue(array('test' => array())));
|
||||
$row = new Row(array(), array());
|
||||
$this->executable->processRow($row);
|
||||
$this->assertSame($row->getDestination(), array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mock migration source instance.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected function getMockSource() {
|
||||
$iterator = $this->getMock('\Iterator');
|
||||
|
||||
$class = 'Drupal\migrate\Plugin\migrate\source\SourcePluginBase';
|
||||
$source = $this->getMockBuilder($class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(get_class_methods($class))
|
||||
->getMockForAbstractClass();
|
||||
$source->expects($this->any())
|
||||
->method('getIterator')
|
||||
->will($this->returnValue($iterator));
|
||||
$source->expects($this->once())
|
||||
->method('rewind')
|
||||
->will($this->returnValue(TRUE));
|
||||
$source->expects($this->any())
|
||||
->method('initializeIterator')
|
||||
->will($this->returnValue([]));
|
||||
$source->expects($this->any())
|
||||
->method('valid')
|
||||
->will($this->onConsecutiveCalls(TRUE, FALSE));
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
}
|
245
core/modules/migrate/tests/src/Unit/MigrateSourceTest.php
Normal file
245
core/modules/migrate/tests/src/Unit/MigrateSourceTest.php
Normal file
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrateSourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateSourceTest extends MigrateTestCase {
|
||||
|
||||
/**
|
||||
* Override the migration config.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultMigrationConfiguration = [
|
||||
'id' => 'test_migration',
|
||||
'source' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Test row data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $row = ['test_sourceid1' => '1', 'timestamp' => 500];
|
||||
|
||||
/**
|
||||
* Test source ids.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIds = ['test_sourceid1' => 'test_sourceid1'];
|
||||
|
||||
/**
|
||||
* The migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Entity\Migration
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The migrate executable.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateExecutable
|
||||
*/
|
||||
protected $executable;
|
||||
|
||||
/**
|
||||
* Get the source plugin to test.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The source configuration.
|
||||
* @param array $migrate_config
|
||||
* The migration configuration to be used in parent::getMigration().
|
||||
* @param int $status
|
||||
* The default status for the new rows to be imported.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* A mocked source plugin.
|
||||
*/
|
||||
protected function getSource($configuration = [], $migrate_config = [], $status = MigrateIdMapInterface::STATUS_NEEDS_UPDATE) {
|
||||
$this->migrationConfiguration = $this->defaultMigrationConfiguration + $migrate_config;
|
||||
$this->migration = parent::getMigration();
|
||||
$this->executable = $this->getMigrateExecutable($this->migration);
|
||||
|
||||
// Update the idMap for Source so the default is that the row has already
|
||||
// been imported. This allows us to use the highwater mark to decide on the
|
||||
// outcome of whether we choose to import the row.
|
||||
$id_map_array = ['original_hash' => '', 'hash' => '', 'source_row_status' => $status];
|
||||
$this->idMap
|
||||
->expects($this->any())
|
||||
->method('getRowBySource')
|
||||
->willReturn($id_map_array);
|
||||
|
||||
$constructor_args = [$configuration, 'd6_action', [], $this->migration];
|
||||
$methods = ['getModuleHandler', 'fields', 'getIds', '__toString', 'getIterator', 'prepareRow', 'initializeIterator', 'calculateDependencies'];
|
||||
$source_plugin = $this->getMock('\Drupal\migrate\Plugin\migrate\source\SourcePluginBase', $methods, $constructor_args);
|
||||
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('fields')
|
||||
->willReturn([]);
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('getIds')
|
||||
->willReturn([]);
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('__toString')
|
||||
->willReturn('');
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('prepareRow')
|
||||
->willReturn(empty($migrate_config['prepare_row_false']));
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('initializeIterator')
|
||||
->willReturn([]);
|
||||
$iterator = new \ArrayIterator([$this->row]);
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('getIterator')
|
||||
->willReturn($iterator);
|
||||
|
||||
$module_handler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('getModuleHandler')
|
||||
->willReturn($module_handler);
|
||||
|
||||
$this->migration
|
||||
->expects($this->any())
|
||||
->method('getSourcePlugin')
|
||||
->willReturn($source_plugin);
|
||||
|
||||
return $this->migration->getSourcePlugin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function testHighwaterTrackChangesIncompatible() {
|
||||
$source_config = ['track_changes' => TRUE];
|
||||
$migration_config = ['highWaterProperty' => ['name' => 'something']];
|
||||
$this->getSource($source_config, $migration_config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the source count is correct.
|
||||
*/
|
||||
public function testCount() {
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('cache.migrate', 'Drupal\Core\Cache\NullBackend')
|
||||
->setArguments(['migrate']);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
// Test that the basic count works.
|
||||
$source = $this->getSource();
|
||||
$this->assertEquals(1, $source->count());
|
||||
|
||||
// Test caching the count works.
|
||||
$source = $this->getSource(['cache_counts' => TRUE]);
|
||||
$this->assertEquals(1, $source->count());
|
||||
|
||||
// Test the skip argument.
|
||||
$source = $this->getSource(['skip_count' => TRUE]);
|
||||
$this->assertEquals(-1, $source->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we don't get a row if prepareRow() is false.
|
||||
*/
|
||||
public function testPrepareRowFalse() {
|
||||
$source = $this->getSource([], ['prepare_row_false' => TRUE]);
|
||||
|
||||
$source->rewind();
|
||||
$this->assertNull($source->current(), 'No row is available when prepareRow() is false.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the when a source id is in the idList, we don't get a row.
|
||||
*/
|
||||
public function testIdInList() {
|
||||
$source = $this->getSource([], ['idlist' => ['test_sourceid1']]);
|
||||
$source->rewind();
|
||||
|
||||
$this->assertNull($source->current(), 'No row is available because id was in idList.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that $row->needsUpdate() works as expected.
|
||||
*/
|
||||
public function testNextNeedsUpdate() {
|
||||
$source = $this->getSource();
|
||||
|
||||
// $row->needsUpdate() === TRUE so we get a row.
|
||||
$source->rewind();
|
||||
$this->assertTrue(is_a($source->current(), 'Drupal\migrate\Row'), '$row->needsUpdate() is TRUE so we got a row.');
|
||||
|
||||
// Test that we don't get a row when the incoming row is marked as imported.
|
||||
$source = $this->getSource([], [], MigrateIdMapInterface::STATUS_IMPORTED);
|
||||
$source->rewind();
|
||||
$this->assertNull($source->current(), 'Row was already imported, should be NULL');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an outdated highwater mark does not cause a row to be imported.
|
||||
*/
|
||||
public function testOutdatedHighwater() {
|
||||
|
||||
$source = $this->getSource([], [], MigrateIdMapInterface::STATUS_IMPORTED);
|
||||
|
||||
// Set the originalHighwater to something higher than our timestamp.
|
||||
$this->migration
|
||||
->expects($this->any())
|
||||
->method('getHighwater')
|
||||
->willReturn($this->row['timestamp'] + 1);
|
||||
|
||||
// The current highwater mark is now higher than the row timestamp so no row
|
||||
// is expected.
|
||||
$source->rewind();
|
||||
$this->assertNull($source->current(), 'Original highwater mark is higher than incoming row timestamp.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a highwater mark newer than our saved one imports a row.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testNewHighwater() {
|
||||
|
||||
// Set a highwater property field for source. Now we should have a row
|
||||
// because the row timestamp is greater than the current highwater mark.
|
||||
$source = $this->getSource([], ['highWaterProperty' => ['name' => 'timestamp']], MigrateIdMapInterface::STATUS_IMPORTED);
|
||||
|
||||
$source->rewind();
|
||||
$this->assertTrue(is_a($source->current(), 'Drupal\migrate\Row'), 'Incoming row timestamp is greater than current highwater mark so we have a row.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mock executable for the test.
|
||||
*
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration entity.
|
||||
*
|
||||
* @return \Drupal\migrate\MigrateExecutable
|
||||
* The migrate executable.
|
||||
*/
|
||||
protected function getMigrateExecutable($migration) {
|
||||
$message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
|
||||
return new MigrateExecutable($migration, $message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrateSqlIdMapEnsureTablesTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
|
||||
/**
|
||||
* Tests the SQL ID map plugin ensureTables() method.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateSqlIdMapEnsureTablesTest extends MigrateTestCase {
|
||||
|
||||
/**
|
||||
* The migration configuration, initialized to set the ID and destination IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migrationConfiguration = array(
|
||||
'id' => 'sql_idmap_test',
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests the ensureTables method when the tables do not exist.
|
||||
*/
|
||||
public function testEnsureTablesNotExist() {
|
||||
$fields['source_row_status'] = array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
'description' => 'Indicates current status of the source row',
|
||||
);
|
||||
$fields['rollback_action'] = array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => MigrateIdMapInterface::ROLLBACK_DELETE,
|
||||
'description' => 'Flag indicating what to do for this item on rollback',
|
||||
);
|
||||
$fields['last_imported'] = array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'UNIX timestamp of the last time this row was imported',
|
||||
);
|
||||
$fields['hash'] = array(
|
||||
'type' => 'varchar',
|
||||
'length' => '64',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Hash of source row data, for detecting changes',
|
||||
);
|
||||
$fields['sourceid1'] = array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$fields['destid1'] = array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
);
|
||||
$map_table_schema = array(
|
||||
'description' => 'Mappings from source identifier value(s) to destination identifier value(s).',
|
||||
'fields' => $fields,
|
||||
'primary key' => array('sourceid1'),
|
||||
);
|
||||
$schema = $this->getMockBuilder('Drupal\Core\Database\Schema')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$schema->expects($this->at(0))
|
||||
->method('tableExists')
|
||||
->with('migrate_map_sql_idmap_test')
|
||||
->will($this->returnValue(FALSE));
|
||||
$schema->expects($this->at(1))
|
||||
->method('createTable')
|
||||
->with('migrate_map_sql_idmap_test', $map_table_schema);
|
||||
// Now do the message table.
|
||||
$fields = array();
|
||||
$fields['msgid'] = array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$fields['sourceid1'] = array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$fields['level'] = array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
);
|
||||
$fields['message'] = array(
|
||||
'type' => 'text',
|
||||
'size' => 'medium',
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$table_schema = array(
|
||||
'description' => 'Messages generated during a migration process',
|
||||
'fields' => $fields,
|
||||
'primary key' => array('msgid'),
|
||||
);
|
||||
$table_schema['indexes']['sourcekey'] = array('sourceid1');
|
||||
|
||||
$schema->expects($this->at(2))
|
||||
->method('tableExists')
|
||||
->with('migrate_message_sql_idmap_test')
|
||||
->will($this->returnValue(FALSE));
|
||||
$schema->expects($this->at(3))
|
||||
->method('createTable')
|
||||
->with('migrate_message_sql_idmap_test', $table_schema);
|
||||
$schema->expects($this->any())
|
||||
->method($this->anything());
|
||||
$this->runEnsureTablesTest($schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ensureTables method when the tables exist.
|
||||
*/
|
||||
public function testEnsureTablesExist() {
|
||||
$schema = $this->getMockBuilder('Drupal\Core\Database\Schema')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$schema->expects($this->at(0))
|
||||
->method('tableExists')
|
||||
->with('migrate_map_sql_idmap_test')
|
||||
->will($this->returnValue(TRUE));
|
||||
$schema->expects($this->at(1))
|
||||
->method('fieldExists')
|
||||
->with('migrate_map_sql_idmap_test', 'rollback_action')
|
||||
->will($this->returnValue(FALSE));
|
||||
$field_schema = array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Flag indicating what to do for this item on rollback',
|
||||
);
|
||||
$schema->expects($this->at(2))
|
||||
->method('addField')
|
||||
->with('migrate_map_sql_idmap_test', 'rollback_action', $field_schema);
|
||||
$schema->expects($this->at(3))
|
||||
->method('fieldExists')
|
||||
->with('migrate_map_sql_idmap_test', 'hash')
|
||||
->will($this->returnValue(FALSE));
|
||||
$field_schema = array(
|
||||
'type' => 'varchar',
|
||||
'length' => '64',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Hash of source row data, for detecting changes',
|
||||
);
|
||||
$schema->expects($this->at(4))
|
||||
->method('addField')
|
||||
->with('migrate_map_sql_idmap_test', 'hash', $field_schema);
|
||||
$schema->expects($this->exactly(5))
|
||||
->method($this->anything());
|
||||
$this->runEnsureTablesTest($schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually run the test.
|
||||
*
|
||||
* @param array $schema
|
||||
* The mock schema object with expectations set. The Sql constructor calls
|
||||
* ensureTables() which in turn calls this object and the expectations on
|
||||
* it are the actual test and there are no additional asserts added.
|
||||
*/
|
||||
protected function runEnsureTablesTest($schema) {
|
||||
$database = $this->getMockBuilder('Drupal\Core\Database\Connection')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$database->expects($this->any())
|
||||
->method('schema')
|
||||
->will($this->returnValue($schema));
|
||||
$migration = $this->getMigration();
|
||||
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
|
||||
$plugin->expects($this->any())
|
||||
->method('getIds')
|
||||
->will($this->returnValue(array(
|
||||
'source_id_property' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
)));
|
||||
$migration->expects($this->any())
|
||||
->method('getSourcePlugin')
|
||||
->will($this->returnValue($plugin));
|
||||
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
|
||||
$plugin->expects($this->any())
|
||||
->method('getIds')
|
||||
->will($this->returnValue(array(
|
||||
'destination_id_property' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
)));
|
||||
$migration->expects($this->any())
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($plugin));
|
||||
$map = new TestSqlIdMap($database, array(), 'sql', array(), $migration);
|
||||
$map->getDatabase();
|
||||
}
|
||||
|
||||
}
|
798
core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
Normal file
798
core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
Normal file
|
@ -0,0 +1,798 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrateSqlIdMapTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Core\Database\Driver\sqlite\Connection;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Tests the SQL ID map plugin.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateSqlIdMapTest extends MigrateTestCase {
|
||||
|
||||
/**
|
||||
* The migration configuration, initialized to set the ID and destination IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migrationConfiguration = [
|
||||
'id' => 'sql_idmap_test',
|
||||
];
|
||||
|
||||
protected $sourceIds = [
|
||||
'source_id_property' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
|
||||
protected $destinationIds = [
|
||||
'destination_id_property' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
public function setUp() {
|
||||
$this->database = $this->getDatabase([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a single ID mapping row in the database.
|
||||
*
|
||||
* @param array $map
|
||||
* The row to save.
|
||||
*/
|
||||
protected function saveMap(array $map) {
|
||||
$table = 'migrate_map_sql_idmap_test';
|
||||
|
||||
$schema = $this->database->schema();
|
||||
// If the table already exists, add any columns which are in the map array,
|
||||
// but don't yet exist in the table. Yay, flexibility!
|
||||
if ($schema->tableExists($table)) {
|
||||
foreach (array_keys($map) as $field) {
|
||||
if (!$schema->fieldExists($table, $field)) {
|
||||
$schema->addField($table, $field, ['type' => 'text']);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$schema->createTable($table, $this->createSchemaFromRow($map));
|
||||
}
|
||||
|
||||
$this->database->insert($table)->fields($map)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test SQL ID map plugin.
|
||||
*
|
||||
* @return \Drupal\Tests\migrate\Unit\TestSqlIdMap
|
||||
* A SQL ID map plugin test instance.
|
||||
*/
|
||||
protected function getIdMap() {
|
||||
$migration = $this->getMigration();
|
||||
|
||||
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
|
||||
$plugin
|
||||
->method('getIds')
|
||||
->willReturn($this->sourceIds);
|
||||
$migration
|
||||
->method('getSourcePlugin')
|
||||
->willReturn($plugin);
|
||||
|
||||
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
|
||||
$plugin
|
||||
->method('getIds')
|
||||
->willReturn($this->destinationIds);
|
||||
$migration
|
||||
->method('getDestinationPlugin')
|
||||
->willReturn($plugin);
|
||||
|
||||
$id_map = new TestSqlIdMap($this->database, [], 'sql', [], $migration);
|
||||
$migration
|
||||
->method('getIdMap')
|
||||
->willReturn($id_map);
|
||||
|
||||
return $id_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets defaults for SQL ID map plugin tests.
|
||||
*/
|
||||
protected function idMapDefaults() {
|
||||
$defaults = array(
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
|
||||
'hash' => '',
|
||||
);
|
||||
// By default, the PDO SQLite driver strongly prefers to return strings
|
||||
// from SELECT queries. Even for columns that don't store strings. Even
|
||||
// if the connection's STRINGIFY_FETCHES attribute is FALSE. This can cause
|
||||
// assertSame() calls to fail, since 0 !== '0'. Casting these values to
|
||||
// strings isn't the most elegant workaround, but it allows the assertions
|
||||
// to pass properly.
|
||||
if ($this->database->driver() == 'sqlite') {
|
||||
$defaults['source_row_status'] = (string) $defaults['source_row_status'];
|
||||
$defaults['rollback_action'] = (string) $defaults['rollback_action'];
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ID mapping method.
|
||||
*
|
||||
* Create two ID mappings and update the second to verify that:
|
||||
* - saving new to empty tables work.
|
||||
* - saving new to nonempty tables work.
|
||||
* - updating work.
|
||||
*/
|
||||
public function testSaveIdMapping() {
|
||||
$source = array(
|
||||
'source_id_property' => 'source_value',
|
||||
);
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$id_map = $this->getIdMap();
|
||||
$id_map->saveIdMapping($row, ['destination_id_property' => 2]);
|
||||
$expected_result = [
|
||||
[
|
||||
'sourceid1' => 'source_value',
|
||||
'destid1' => 2,
|
||||
] + $this->idMapDefaults(),
|
||||
];
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_result);
|
||||
$source = [
|
||||
'source_id_property' => 'source_value_1',
|
||||
];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$id_map->saveIdMapping($row, ['destination_id_property' => 3]);
|
||||
$expected_result[] = [
|
||||
'sourceid1' => 'source_value_1',
|
||||
'destid1' => 3,
|
||||
] + $this->idMapDefaults();
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_result);
|
||||
$id_map->saveIdMapping($row, ['destination_id_property' => 4]);
|
||||
$expected_result[1]['destid1'] = 4;
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the SQL ID map set message method.
|
||||
*/
|
||||
public function testSetMessage() {
|
||||
$message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
|
||||
$id_map = $this->getIdMap();
|
||||
$id_map->setMessage($message);
|
||||
$this->assertAttributeEquals($message, 'message', $id_map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the clear messages method.
|
||||
*/
|
||||
public function testClearMessages() {
|
||||
$message = 'Hello world.';
|
||||
$expected_results = [0, 1, 2, 3];
|
||||
$id_map = $this->getIdMap();
|
||||
|
||||
// Insert 4 message for later delete.
|
||||
foreach ($expected_results as $key => $expected_result) {
|
||||
$id_map->saveMessage([$key], $message);
|
||||
}
|
||||
|
||||
// Truncate and check that 4 messages were deleted.
|
||||
$this->assertEquals($id_map->messageCount(), 4);
|
||||
$id_map->clearMessages();
|
||||
$count = $id_map->messageCount();
|
||||
$this->assertEquals($count, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getRowsNeedingUpdate method for rows that need an update.
|
||||
*/
|
||||
public function testGetRowsNeedingUpdate() {
|
||||
$id_map = $this->getIdMap();
|
||||
$row_statuses = [
|
||||
MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
MigrateIdMapInterface::STATUS_IGNORED,
|
||||
MigrateIdMapInterface::STATUS_FAILED,
|
||||
];
|
||||
// Create a mapping row for each STATUS constant.
|
||||
foreach ($row_statuses as $status) {
|
||||
$source = ['source_id_property' => 'source_value_' . $status];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_' . $status];
|
||||
$id_map->saveIdMapping($row, $destination, $status);
|
||||
$expected_results[] = [
|
||||
'sourceid1' => 'source_value_' . $status,
|
||||
'destid1' => 'destination_value_' . $status,
|
||||
'source_row_status' => $status,
|
||||
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
|
||||
'hash' => '',
|
||||
];
|
||||
// Assert zero rows need an update.
|
||||
if ($status == MigrateIdMapInterface::STATUS_IMPORTED) {
|
||||
$rows_needing_update = $id_map->getRowsNeedingUpdate(1);
|
||||
$this->assertCount(0, $rows_needing_update);
|
||||
}
|
||||
}
|
||||
// Assert that test values exist.
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_results);
|
||||
|
||||
// Assert a single row needs an update.
|
||||
$row_needing_update = $id_map->getRowsNeedingUpdate(1);
|
||||
$this->assertCount(1, $row_needing_update);
|
||||
|
||||
// Assert the row matches its original source.
|
||||
$source_id = $expected_results[MigrateIdMapInterface::STATUS_NEEDS_UPDATE]['sourceid1'];
|
||||
$test_row = $id_map->getRowBySource([$source_id]);
|
||||
// $row_needing_update is an array of objects returned from the database,
|
||||
// but $test_row is an array, so the cast is necessary.
|
||||
$this->assertSame($test_row, (array) $row_needing_update[0]);
|
||||
|
||||
// Add additional row that needs an update.
|
||||
$source = ['source_id_property' => 'source_value_multiple'];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_multiple'];
|
||||
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
|
||||
// Assert multiple rows need an update.
|
||||
$rows_needing_update = $id_map->getRowsNeedingUpdate(2);
|
||||
$this->assertCount(2, $rows_needing_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the SQL ID map message count method by counting and saving messages.
|
||||
*/
|
||||
public function testMessageCount() {
|
||||
$message = 'Hello world.';
|
||||
$expected_results = [0, 1, 2, 3];
|
||||
$id_map = $this->getIdMap();
|
||||
|
||||
// Test count message multiple times starting from 0.
|
||||
foreach ($expected_results as $key => $expected_result) {
|
||||
$count = $id_map->messageCount();
|
||||
$this->assertEquals($expected_result, $count);
|
||||
$id_map->saveMessage([$key], $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the SQL ID map save message method.
|
||||
*/
|
||||
public function testMessageSave() {
|
||||
$message = 'Hello world.';
|
||||
$expected_results = [
|
||||
1 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_ERROR],
|
||||
2 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_WARNING],
|
||||
3 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_NOTICE],
|
||||
4 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_INFORMATIONAL],
|
||||
];
|
||||
$id_map = $this->getIdMap();
|
||||
|
||||
foreach ($expected_results as $key => $expected_result) {
|
||||
$id_map->saveMessage([$key], $message, $expected_result['level']);
|
||||
$message_row = $this->database->select($id_map->messageTableName(), 'message')
|
||||
->fields('message')
|
||||
->condition('level', $expected_result['level'])
|
||||
->condition('message', $expected_result['message'])
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
$this->assertEquals($expected_result['message'], $message_row['message'], 'Message from database was read.');
|
||||
}
|
||||
|
||||
// Insert with default level.
|
||||
$message_default = 'Hello world default.';
|
||||
$id_map->saveMessage([5], $message_default);
|
||||
$message_row = $this->database->select($id_map->messageTableName(), 'message')
|
||||
->fields('message')
|
||||
->condition('level', MigrationInterface::MESSAGE_ERROR)
|
||||
->condition('message', $message_default)
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
$this->assertEquals($message_default, $message_row['message'], 'Message from database was read.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getRowBySource method.
|
||||
*/
|
||||
public function testGetRowBySource() {
|
||||
$this->getDatabase([]);
|
||||
$row = [
|
||||
'sourceid1' => 'source_id_value_1',
|
||||
'sourceid2' => 'source_id_value_2',
|
||||
'destid1' => 'destination_id_value_1',
|
||||
] + $this->idMapDefaults();
|
||||
$this->saveMap($row);
|
||||
$row = [
|
||||
'sourceid1' => 'source_id_value_3',
|
||||
'sourceid2' => 'source_id_value_4',
|
||||
'destid1' => 'destination_id_value_2',
|
||||
] + $this->idMapDefaults();
|
||||
$this->saveMap($row);
|
||||
$source_id_values = [$row['sourceid1'], $row['sourceid2']];
|
||||
$id_map = $this->getIdMap();
|
||||
$result_row = $id_map->getRowBySource($source_id_values);
|
||||
$this->assertSame($row, $result_row);
|
||||
$source_id_values = ['missing_value_1', 'missing_value_2'];
|
||||
$result_row = $id_map->getRowBySource($source_id_values);
|
||||
$this->assertFalse($result_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testLookupDestinationIdMapping().
|
||||
*
|
||||
* Scenarios to test (for both hits and misses) are:
|
||||
* - Single-value source ID to single-value destination ID.
|
||||
* - Multi-value source ID to multi-value destination ID.
|
||||
* - Single-value source ID to multi-value destination ID.
|
||||
* - Multi-value source ID to single-value destination ID.
|
||||
*/
|
||||
public function lookupDestinationIdMappingDataProvider() {
|
||||
return [
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[1, 2],
|
||||
[2, 1],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs destination ID test on source and destination fields.
|
||||
*
|
||||
* @param int $num_source_fields
|
||||
* Number of source fields to test.
|
||||
* @param int $num_destination_fields
|
||||
* Number of destination fields to test.
|
||||
* @dataProvider lookupDestinationIdMappingDataProvider
|
||||
*/
|
||||
public function testLookupDestinationIdMapping($num_source_fields, $num_destination_fields) {
|
||||
// Adjust the migration configuration according to the number of source and
|
||||
// destination fields.
|
||||
$this->sourceIds = [];
|
||||
$this->destinationIds = [];
|
||||
$source_id_values = [];
|
||||
$nonexistent_id_values = [];
|
||||
$row = $this->idMapDefaults();
|
||||
for ($i = 1; $i <= $num_source_fields; $i++) {
|
||||
$row["sourceid$i"] = "source_id_value_$i";
|
||||
$source_id_values[] = "source_id_value_$i";
|
||||
$nonexistent_id_values[] = "nonexistent_source_id_value_$i";
|
||||
$this->sourceIds["source_id_property_$i"] = [];
|
||||
}
|
||||
$expected_result = [];
|
||||
for ($i = 1; $i <= $num_destination_fields; $i++) {
|
||||
$row["destid$i"] = "destination_id_value_$i";
|
||||
$expected_result[] = "destination_id_value_$i";
|
||||
$this->destinationIds["destination_id_property_$i"] = [];
|
||||
}
|
||||
$this->saveMap($row);
|
||||
$id_map = $this->getIdMap();
|
||||
// Test for a valid hit.
|
||||
$destination_id = $id_map->lookupDestinationId($source_id_values);
|
||||
$this->assertSame($expected_result, $destination_id);
|
||||
// Test for a miss.
|
||||
$destination_id = $id_map->lookupDestinationId($nonexistent_id_values);
|
||||
$this->assertSame(0, count($destination_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getRowByDestination method.
|
||||
*/
|
||||
public function testGetRowByDestination() {
|
||||
$row = [
|
||||
'sourceid1' => 'source_id_value_1',
|
||||
'sourceid2' => 'source_id_value_2',
|
||||
'destid1' => 'destination_id_value_1',
|
||||
] + $this->idMapDefaults();
|
||||
$this->saveMap($row);
|
||||
$row = [
|
||||
'sourceid1' => 'source_id_value_3',
|
||||
'sourceid2' => 'source_id_value_4',
|
||||
'destid1' => 'destination_id_value_2',
|
||||
] + $this->idMapDefaults();
|
||||
$this->saveMap($row);
|
||||
$dest_id_values = [$row['destid1']];
|
||||
$id_map = $this->getIdMap();
|
||||
$result_row = $id_map->getRowByDestination($dest_id_values);
|
||||
$this->assertSame($row, $result_row);
|
||||
// This value does not exist.
|
||||
$dest_id_values = ['invalid_destination_id_property'];
|
||||
$id_map = $this->getIdMap();
|
||||
$result_row = $id_map->getRowByDestination($dest_id_values);
|
||||
$this->assertFalse($result_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testLookupSourceIDMapping().
|
||||
*
|
||||
* Scenarios to test (for both hits and misses) are:
|
||||
* - Single-value destination ID to single-value source ID.
|
||||
* - Multi-value destination ID to multi-value source ID.
|
||||
* - Single-value destination ID to multi-value source ID.
|
||||
* - Multi-value destination ID to single-value source ID.
|
||||
*/
|
||||
public function lookupSourceIDMappingDataProvider() {
|
||||
return [
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[1, 2],
|
||||
[2, 1],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the source ID test on source and destination fields.
|
||||
*
|
||||
* @param int $num_source_fields
|
||||
* Number of source fields to test.
|
||||
* @param int $num_destination_fields
|
||||
* Number of destination fields to test.
|
||||
* @dataProvider lookupSourceIDMappingDataProvider
|
||||
*/
|
||||
public function testLookupSourceIDMapping($num_source_fields, $num_destination_fields) {
|
||||
// Adjust the migration configuration according to the number of source and
|
||||
// destination fields.
|
||||
$this->sourceIds = [];
|
||||
$this->destinationIds = [];
|
||||
$row = $this->idMapDefaults();
|
||||
$expected_result = [];
|
||||
for ($i = 1; $i <= $num_source_fields; $i++) {
|
||||
$row["sourceid$i"] = "source_id_value_$i";
|
||||
$expected_result[] = "source_id_value_$i";
|
||||
$this->sourceIds["source_id_property_$i"] = [];
|
||||
}
|
||||
$destination_id_values = [];
|
||||
$nonexistent_id_values = [];
|
||||
for ($i = 1; $i <= $num_destination_fields; $i++) {
|
||||
$row["destid$i"] = "destination_id_value_$i";
|
||||
$destination_id_values[] = "destination_id_value_$i";
|
||||
$nonexistent_id_values[] = "nonexistent_destination_id_value_$i";
|
||||
$this->destinationIds["destination_id_property_$i"] = [];
|
||||
}
|
||||
$this->saveMap($row);
|
||||
$id_map = $this->getIdMap();
|
||||
// Test for a valid hit.
|
||||
$source_id = $id_map->lookupSourceID($destination_id_values);
|
||||
$this->assertSame($expected_result, $source_id);
|
||||
// Test for a miss.
|
||||
$source_id = $id_map->lookupSourceID($nonexistent_id_values);
|
||||
$this->assertSame(0, count($source_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the imported count method.
|
||||
*
|
||||
* Scenarios to test for:
|
||||
* - No imports.
|
||||
* - One import.
|
||||
* - Multiple imports.
|
||||
*/
|
||||
public function testImportedCount() {
|
||||
$id_map = $this->getIdMap();
|
||||
// Add a single failed row and assert zero imported rows.
|
||||
$source = ['source_id_property' => 'source_value_failed'];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_failed'];
|
||||
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_FAILED);
|
||||
$this->assertSame(0, (int) $id_map->importedCount());
|
||||
|
||||
// Add an imported row and assert single count.
|
||||
$source = ['source_id_property' => 'source_value_imported'];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_imported'];
|
||||
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_IMPORTED);
|
||||
$this->assertSame(1, (int) $id_map->importedCount());
|
||||
|
||||
// Add a row needing update and assert multiple imported rows.
|
||||
$source = ['source_id_property' => 'source_value_update'];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_update'];
|
||||
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
$this->assertSame(2, (int) $id_map->importedCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the number of processed source rows.
|
||||
*
|
||||
* Scenarios to test for:
|
||||
* - No processed rows.
|
||||
* - One processed row.
|
||||
* - Multiple processed rows.
|
||||
*/
|
||||
public function testProcessedCount() {
|
||||
$id_map = $this->getIdMap();
|
||||
// Assert zero rows have been processed before adding rows.
|
||||
$this->assertSame(0, (int) $id_map->processedCount());
|
||||
$row_statuses = [
|
||||
MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
MigrateIdMapInterface::STATUS_IGNORED,
|
||||
MigrateIdMapInterface::STATUS_FAILED,
|
||||
];
|
||||
// Create a mapping row for each STATUS constant.
|
||||
foreach ($row_statuses as $status) {
|
||||
$source = ['source_id_property' => 'source_value_' . $status];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_' . $status];
|
||||
$id_map->saveIdMapping($row, $destination, $status);
|
||||
if ($status == MigrateIdMapInterface::STATUS_IMPORTED) {
|
||||
// Assert a single row has been processed.
|
||||
$this->assertSame(1, (int) $id_map->processedCount());
|
||||
}
|
||||
}
|
||||
// Assert multiple rows have been processed.
|
||||
$this->assertSame(count($row_statuses), (int) $id_map->processedCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testUpdateCount().
|
||||
*
|
||||
* Scenarios to test for:
|
||||
* - No updates.
|
||||
* - One update.
|
||||
* - Multiple updates.
|
||||
*/
|
||||
public function updateCountDataProvider() {
|
||||
return [
|
||||
[0],
|
||||
[1],
|
||||
[3],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the update count test with a given number of update rows.
|
||||
*
|
||||
* @param int $num_update_rows
|
||||
* The number of update rows to test.
|
||||
* @dataProvider updateCountDataProvider
|
||||
*/
|
||||
public function testUpdateCount($num_update_rows) {
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$row = $this->idMapDefaults();
|
||||
$row['sourceid1'] = "source_id_value_$i";
|
||||
$row['destid1'] = "destination_id_value_$i";
|
||||
$row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
$this->saveMap($row);
|
||||
}
|
||||
for (; $i < 5 + $num_update_rows; $i++) {
|
||||
$row = $this->idMapDefaults();
|
||||
$row['sourceid1'] = "source_id_value_$i";
|
||||
$row['destid1'] = "destination_id_value_$i";
|
||||
$row['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
|
||||
$this->saveMap($row);
|
||||
}
|
||||
$id_map = $this->getIdMap();
|
||||
$this->assertSame($num_update_rows, (int) $id_map->updateCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testErrorCount().
|
||||
*
|
||||
* Scenarios to test for:
|
||||
* - No errors.
|
||||
* - One error.
|
||||
* - Multiple errors.
|
||||
*/
|
||||
public function errorCountDataProvider() {
|
||||
return [
|
||||
[0],
|
||||
[1],
|
||||
[3],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs error count test with a given number of error rows.
|
||||
*
|
||||
* @param int $num_error_rows
|
||||
* Number of error rows to test.
|
||||
* @dataProvider errorCountDataProvider
|
||||
*/
|
||||
public function testErrorCount($num_error_rows) {
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$row = $this->idMapDefaults();
|
||||
$row['sourceid1'] = "source_id_value_$i";
|
||||
$row['destid1'] = "destination_id_value_$i";
|
||||
$row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
$this->saveMap($row);
|
||||
}
|
||||
for (; $i < 5 + $num_error_rows; $i++) {
|
||||
$row = $this->idMapDefaults();
|
||||
$row['sourceid1'] = "source_id_value_$i";
|
||||
$row['destid1'] = "destination_id_value_$i";
|
||||
$row['source_row_status'] = MigrateIdMapInterface::STATUS_FAILED;
|
||||
$this->saveMap($row);
|
||||
}
|
||||
|
||||
$this->assertSame($num_error_rows, (int) $this->getIdMap()->errorCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting a row source_row_status to STATUS_NEEDS_UPDATE.
|
||||
*/
|
||||
public function testSetUpdate() {
|
||||
$id_map = $this->getIdMap();
|
||||
$row_statuses = [
|
||||
MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
MigrateIdMapInterface::STATUS_IGNORED,
|
||||
MigrateIdMapInterface::STATUS_FAILED,
|
||||
];
|
||||
// Create a mapping row for each STATUS constant.
|
||||
foreach ($row_statuses as $status) {
|
||||
$source = ['source_id_property' => 'source_value_' . $status];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_' . $status];
|
||||
$id_map->saveIdMapping($row, $destination, $status);
|
||||
$expected_results[] = [
|
||||
'sourceid1' => 'source_value_' . $status,
|
||||
'destid1' => 'destination_value_' . $status,
|
||||
'source_row_status' => $status,
|
||||
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
|
||||
'hash' => '',
|
||||
];
|
||||
}
|
||||
// Assert that test values exist.
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_results);
|
||||
// Mark each row as STATUS_NEEDS_UPDATE.
|
||||
foreach ($row_statuses as $status) {
|
||||
$id_map->setUpdate(['source_value_' . $status]);
|
||||
}
|
||||
// Update expected results.
|
||||
foreach ($expected_results as $key => $value) {
|
||||
$expected_results[$key]['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
|
||||
}
|
||||
// Assert that updated expected values match.
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_results);
|
||||
// Assert an exception is thrown when source identifiers are not provided.
|
||||
try {
|
||||
$id_map->setUpdate([]);
|
||||
$this->assertFalse(FALSE, 'MigrateException not thrown, when source identifiers were provided to update.');
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->assertTrue(TRUE, "MigrateException thrown, when source identifiers were not provided to update.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests prepareUpdate().
|
||||
*/
|
||||
public function testPrepareUpdate() {
|
||||
$id_map = $this->getIdMap();
|
||||
$row_statuses = [
|
||||
MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
MigrateIdMapInterface::STATUS_IGNORED,
|
||||
MigrateIdMapInterface::STATUS_FAILED,
|
||||
];
|
||||
|
||||
// Create a mapping row for each STATUS constant.
|
||||
foreach ($row_statuses as $status) {
|
||||
$source = ['source_id_property' => 'source_value_' . $status];
|
||||
$row = new Row($source, ['source_id_property' => []]);
|
||||
$destination = ['destination_id_property' => 'destination_value_' . $status];
|
||||
$id_map->saveIdMapping($row, $destination, $status);
|
||||
$expected_results[] = [
|
||||
'sourceid1' => 'source_value_' . $status,
|
||||
'destid1' => 'destination_value_' . $status,
|
||||
'source_row_status' => $status,
|
||||
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
|
||||
'hash' => '',
|
||||
];
|
||||
}
|
||||
|
||||
// Assert that test values exist.
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_results);
|
||||
|
||||
// Mark all rows as STATUS_NEEDS_UPDATE.
|
||||
$id_map->prepareUpdate();
|
||||
|
||||
// Update expected results.
|
||||
foreach ($expected_results as $key => $value) {
|
||||
$expected_results[$key]['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
|
||||
}
|
||||
// Assert that updated expected values match.
|
||||
$this->queryResultTest($this->getIdMapContents(), $expected_results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the destroy method.
|
||||
*
|
||||
* Scenarios to test for:
|
||||
* - No errors.
|
||||
* - One error.
|
||||
* - Multiple errors.
|
||||
*/
|
||||
public function testDestroy() {
|
||||
$id_map = $this->getIdMap();
|
||||
// Initialize the id map.
|
||||
$id_map->getDatabase();
|
||||
$map_table_name = $id_map->mapTableName();
|
||||
$message_table_name = $id_map->messageTableName();
|
||||
$row = new Row(['source_id_property' => 'source_value'], ['source_id_property' => []]);
|
||||
$id_map->saveIdMapping($row, ['destination_id_property' => 2]);
|
||||
$id_map->saveMessage(['source_value'], 'A message');
|
||||
$this->assertTrue($this->database->schema()->tableExists($map_table_name),
|
||||
"$map_table_name exists");
|
||||
$this->assertTrue($this->database->schema()->tableExists($message_table_name),
|
||||
"$message_table_name exists");
|
||||
$id_map->destroy();
|
||||
$this->assertFalse($this->database->schema()->tableExists($map_table_name),
|
||||
"$map_table_name does not exist");
|
||||
$this->assertFalse($this->database->schema()->tableExists($message_table_name),
|
||||
"$message_table_name does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getQualifiedMapTable method with a prefixed database.
|
||||
*/
|
||||
public function testGetQualifiedMapTablePrefix() {
|
||||
$connection_options = [
|
||||
'database' => ':memory:',
|
||||
'prefix' => 'prefix',
|
||||
];
|
||||
$pdo = Connection::open($connection_options);
|
||||
$this->database = new Connection($pdo, $connection_options);
|
||||
$qualified_map_table = $this->getIdMap()->getQualifiedMapTableName();
|
||||
// The SQLite driver is a special flower. It will prefix tables with
|
||||
// PREFIX.TABLE, instead of the standard PREFIXTABLE.
|
||||
// @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct()
|
||||
$this->assertEquals('prefix.migrate_map_sql_idmap_test', $qualified_map_table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests all the iterator methods in one swing.
|
||||
*
|
||||
* The iterator methods are:
|
||||
* - Sql::rewind()
|
||||
* - Sql::next()
|
||||
* - Sql::valid()
|
||||
* - Sql::key()
|
||||
* - Sql::current()
|
||||
*/
|
||||
public function testIterators() {
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$row = $this->idMapDefaults();
|
||||
$row['sourceid1'] = "source_id_value_$i";
|
||||
$row['destid1'] = "destination_id_value_$i";
|
||||
$row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
$expected_results[serialize(['sourceid1' => $row['sourceid1']])] = ['destid1' => $row['destid1']];
|
||||
$this->saveMap($row);
|
||||
}
|
||||
|
||||
$this->assertSame(iterator_to_array($this->getIdMap()), $expected_results);
|
||||
}
|
||||
|
||||
private function getIdMapContents() {
|
||||
$result = $this->database
|
||||
->select('migrate_map_sql_idmap_test', 't')
|
||||
->fields('t')
|
||||
->execute();
|
||||
|
||||
// The return value needs to be countable, or it will fail certain
|
||||
// assertions. iterator_to_array() will not suffice because it won't
|
||||
// respect the PDO fetch mode, if specified.
|
||||
$contents = [];
|
||||
foreach ($result as $row) {
|
||||
$contents[] = (array) $row;
|
||||
}
|
||||
return $contents;
|
||||
}
|
||||
|
||||
}
|
118
core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php
Normal file
118
core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
/**
|
||||
* Base class for Migrate module source unit tests.
|
||||
*/
|
||||
abstract class MigrateSqlSourceTestCase extends MigrateTestCase {
|
||||
|
||||
/**
|
||||
* The tested source plugin.
|
||||
*
|
||||
* @var \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase.
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* The database contents.
|
||||
*
|
||||
* Database contents represents a mocked database. It should contain an
|
||||
* associative array with the table name as key, and as many nested arrays as
|
||||
* the number of mocked rows. Each of those faked rows must be another array
|
||||
* with the column name as the key and the value as the cell.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseContents = array();
|
||||
|
||||
/**
|
||||
* The plugin class under test.
|
||||
*
|
||||
* The plugin system is not working during unit testing so the source plugin
|
||||
* class needs to be manually specified.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PLUGIN_CLASS = '';
|
||||
|
||||
/**
|
||||
* The high water mark at the beginning of the import operation.
|
||||
*
|
||||
* Once the migration is run, we save a mark of the migrated sources, so the
|
||||
* migration can run again and update only new sources or changed sources.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ORIGINAL_HIGH_WATER = '';
|
||||
|
||||
/**
|
||||
* Expected results after the source parsing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $expectedResults = array();
|
||||
|
||||
/**
|
||||
* The source plugin instance under test.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
|
||||
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
|
||||
|
||||
$migration = $this->getMigration();
|
||||
$migration->expects($this->any())
|
||||
->method('getHighWater')
|
||||
->will($this->returnValue(static::ORIGINAL_HIGH_WATER));
|
||||
|
||||
// Setup the plugin.
|
||||
$plugin_class = static::PLUGIN_CLASS;
|
||||
$plugin = new $plugin_class($this->migrationConfiguration['source'], $this->migrationConfiguration['source']['plugin'], array(), $migration, $entity_manager);
|
||||
|
||||
// Do some reflection to set the database and moduleHandler.
|
||||
$plugin_reflection = new \ReflectionClass($plugin);
|
||||
$database_property = $plugin_reflection->getProperty('database');
|
||||
$database_property->setAccessible(TRUE);
|
||||
$module_handler_property = $plugin_reflection->getProperty('moduleHandler');
|
||||
$module_handler_property->setAccessible(TRUE);
|
||||
|
||||
// Set the database and the module handler onto our plugin.
|
||||
$database_property->setValue($plugin, $this->getDatabase($this->databaseContents + array('test_map' => array())));
|
||||
$module_handler_property->setValue($plugin, $module_handler);
|
||||
|
||||
$plugin->setStringTranslation($this->getStringTranslationStub());
|
||||
$migration->expects($this->any())
|
||||
->method('getSourcePlugin')
|
||||
->will($this->returnValue($plugin));
|
||||
$this->source = $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the source returns the same rows as expected.
|
||||
*/
|
||||
public function testRetrieval() {
|
||||
$this->queryResultTest($this->source, $this->expectedResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getValue($row, $key) {
|
||||
return $row->getSourceProperty($key);
|
||||
}
|
||||
|
||||
}
|
162
core/modules/migrate/tests/src/Unit/MigrateTestCase.php
Normal file
162
core/modules/migrate/tests/src/Unit/MigrateTestCase.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrateTestCase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Core\Database\Driver\sqlite\Connection;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Provides setup and helper methods for Migrate module tests.
|
||||
*/
|
||||
abstract class MigrateTestCase extends UnitTestCase {
|
||||
|
||||
protected $migrationConfiguration = [];
|
||||
|
||||
/**
|
||||
* Retrieve a mocked migration.
|
||||
*
|
||||
* @return \Drupal\migrate\Entity\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
* The mocked migration.
|
||||
*/
|
||||
protected function getMigration() {
|
||||
$this->migrationConfiguration += ['migrationClass' => 'Drupal\migrate\Entity\Migration'];
|
||||
$this->idMap = $this->getMock('Drupal\migrate\Plugin\MigrateIdMapInterface');
|
||||
|
||||
$this->idMap->expects($this->any())
|
||||
->method('getQualifiedMapTableName')
|
||||
->will($this->returnValue('test_map'));
|
||||
|
||||
$migration = $this->getMockBuilder($this->migrationConfiguration['migrationClass'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$migration->expects($this->any())
|
||||
->method('checkRequirements')
|
||||
->will($this->returnValue(TRUE));
|
||||
$migration->expects($this->any())
|
||||
->method('getIdMap')
|
||||
->will($this->returnValue($this->idMap));
|
||||
$configuration = &$this->migrationConfiguration;
|
||||
$migration->expects($this->any())->method('get')->will($this->returnCallback(function ($argument) use (&$configuration) {
|
||||
return isset($configuration[$argument]) ? $configuration[$argument] : '';
|
||||
}));
|
||||
$migration->expects($this->any())->method('set')->will($this->returnCallback(function ($argument, $value) use (&$configuration) {
|
||||
$configuration[$argument] = $value;
|
||||
}));
|
||||
$migration->expects($this->any())
|
||||
->method('id')
|
||||
->will($this->returnValue($configuration['id']));
|
||||
return $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an SQLite database connection object for use in tests.
|
||||
*
|
||||
* @param array $database_contents
|
||||
* The database contents faked as an array. Each key is a table name, each
|
||||
* value is a list of table rows, an associative array of field => value.
|
||||
* @param array $connection_options
|
||||
* (optional) Options for the database connection.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Driver\sqlite\Connection
|
||||
* The database connection.
|
||||
*/
|
||||
protected function getDatabase(array $database_contents, $connection_options = []) {
|
||||
if (extension_loaded('pdo_sqlite')) {
|
||||
$connection_options['database'] = ':memory:';
|
||||
$pdo = Connection::open($connection_options);
|
||||
$connection = new Connection($pdo, $connection_options);
|
||||
}
|
||||
else {
|
||||
$this->markTestSkipped('The pdo_sqlite extension is not available.');
|
||||
}
|
||||
|
||||
// Initialize the DIC with a fake module handler for alterable queries.
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('module_handler', $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface'));
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
// Create the tables and load them up with data, skipping empty ones.
|
||||
foreach (array_filter($database_contents) as $table => $rows) {
|
||||
$pilot_row = reset($rows);
|
||||
$connection->schema()->createTable($table, $this->createSchemaFromRow($pilot_row));
|
||||
|
||||
$insert = $connection->insert($table)->fields(array_keys($pilot_row));
|
||||
array_walk($rows, [$insert, 'values']);
|
||||
$insert->execute();
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a table schema from a row.
|
||||
*
|
||||
* @param array $row
|
||||
* The reference row on which to base the schema.
|
||||
*
|
||||
* @return array
|
||||
* The Schema API-ready table schema.
|
||||
*/
|
||||
protected function createSchemaFromRow(array $row) {
|
||||
// SQLite uses loose ("affinity") typing, so it's OK for every column
|
||||
// to be a text field.
|
||||
$fields = array_map(function() { return ['type' => 'text']; }, $row);
|
||||
return ['fields' => $fields];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a query
|
||||
*
|
||||
* @param array|\Traversable
|
||||
* The countable. foreach-able actual results if a query is being run.
|
||||
*/
|
||||
public function queryResultTest($iter, $expected_results) {
|
||||
$this->assertSame(count($expected_results), count($iter), 'Number of results match');
|
||||
$count = 0;
|
||||
foreach ($iter as $data_row) {
|
||||
$expected_row = $expected_results[$count];
|
||||
$count++;
|
||||
foreach ($expected_row as $key => $expected_value) {
|
||||
$this->retrievalAssertHelper($expected_value, $this->getValue($data_row, $key), sprintf('Value matches for key "%s"', $key));
|
||||
}
|
||||
}
|
||||
$this->assertSame(count($expected_results), $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $row
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getValue($row, $key) {
|
||||
return $row[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts tested values during test retrieval.
|
||||
*
|
||||
* @param mixed $expected_value
|
||||
* The incoming expected value to test.
|
||||
* @param mixed $actual_value
|
||||
* The incoming value itself.
|
||||
* @param string $message
|
||||
* The tested result as a formatted string.
|
||||
*/
|
||||
protected function retrievalAssertHelper($expected_value, $actual_value, $message) {
|
||||
if (is_array($expected_value)) {
|
||||
foreach ($expected_value as $k => $v) {
|
||||
$this->retrievalAssertHelper($v, $actual_value[$k], $message . '[' . $k . ']');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->assertSame((string) $expected_value, (string) $actual_value, $message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
148
core/modules/migrate/tests/src/Unit/MigrationTest.php
Normal file
148
core/modules/migrate/tests/src/Unit/MigrationTest.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\migrate\Entity\Migration;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrateDestinationInterface;
|
||||
use Drupal\migrate\Plugin\MigrateSourceInterface;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Entity\Migration
|
||||
* @group Migration
|
||||
*/
|
||||
class MigrationTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests checking requirements for source plugins.
|
||||
*
|
||||
* @covers ::checkRequirements
|
||||
*
|
||||
* @expectedException \Drupal\migrate\Exception\RequirementsException
|
||||
* @expectedExceptionMessage Missing source requirement
|
||||
*/
|
||||
public function testRequirementsForSourcePlugin() {
|
||||
$migration = new TestMigration();
|
||||
|
||||
$source_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareSourceInterface');
|
||||
$source_plugin->expects($this->once())
|
||||
->method('checkRequirements')
|
||||
->willThrowException(new RequirementsException('Missing source requirement', ['key' => 'value']));
|
||||
$destination_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface');
|
||||
|
||||
$migration->setSourcePlugin($source_plugin);
|
||||
$migration->setDestinationPlugin($destination_plugin);
|
||||
|
||||
$migration->checkRequirements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests checking requirements for destination plugins.
|
||||
*
|
||||
* @covers ::checkRequirements
|
||||
*
|
||||
* @expectedException \Drupal\migrate\Exception\RequirementsException
|
||||
* @expectedExceptionMessage Missing destination requirement
|
||||
*/
|
||||
public function testRequirementsForDestinationPlugin() {
|
||||
$migration = new TestMigration();
|
||||
|
||||
$source_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
|
||||
$destination_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface');
|
||||
$destination_plugin->expects($this->once())
|
||||
->method('checkRequirements')
|
||||
->willThrowException(new RequirementsException('Missing destination requirement', ['key' => 'value']));
|
||||
|
||||
$migration->setSourcePlugin($source_plugin);
|
||||
$migration->setDestinationPlugin($destination_plugin);
|
||||
|
||||
$migration->checkRequirements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests checking requirements for destination plugins.
|
||||
*
|
||||
* @covers ::checkRequirements
|
||||
*
|
||||
* @expectedException \Drupal\migrate\Exception\RequirementsException
|
||||
* @expectedExceptionMessage Missing migrations test_a, test_c
|
||||
*/
|
||||
public function testRequirementsForMigrations() {
|
||||
$migration = new TestMigration();
|
||||
|
||||
// Setup source and destination plugins without any requirements.
|
||||
$source_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
|
||||
$destination_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
|
||||
$migration->setSourcePlugin($source_plugin);
|
||||
$migration->setDestinationPlugin($destination_plugin);
|
||||
|
||||
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
|
||||
$migration->setEntityManager($entity_manager);
|
||||
|
||||
// We setup the requirements that test_a doesn't exist and test_c is not
|
||||
// completed yet.
|
||||
$migration->setRequirements(['test_a', 'test_b', 'test_c', 'test_d']);
|
||||
|
||||
$migration_b = $this->getMock('Drupal\migrate\Entity\MigrationInterface');
|
||||
$migration_c = $this->getMock('Drupal\migrate\Entity\MigrationInterface');
|
||||
$migration_d = $this->getMock('Drupal\migrate\Entity\MigrationInterface');
|
||||
|
||||
$migration_b->expects($this->once())
|
||||
->method('isComplete')
|
||||
->willReturn(TRUE);
|
||||
$migration_c->expects($this->once())
|
||||
->method('isComplete')
|
||||
->willReturn(FALSE);
|
||||
$migration_d->expects($this->once())
|
||||
->method('isComplete')
|
||||
->willReturn(TRUE);
|
||||
|
||||
$migration_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
|
||||
$migration_storage->expects($this->once())
|
||||
->method('loadMultiple')
|
||||
->with(['test_a', 'test_b', 'test_c', 'test_d'])
|
||||
->willReturn(['test_b' => $migration_b, 'test_c' => $migration_c, 'test_d' => $migration_d]);
|
||||
$entity_manager->expects($this->once())
|
||||
->method('getStorage')
|
||||
->with('migration')
|
||||
->willReturn($migration_storage);
|
||||
|
||||
$migration->checkRequirements();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestMigration extends Migration {
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
public function setRequirements(array $requirements) {
|
||||
$this->requirements = $requirements;
|
||||
}
|
||||
|
||||
public function setSourcePlugin(MigrateSourceInterface $source_plugin) {
|
||||
$this->sourcePlugin = $source_plugin;
|
||||
}
|
||||
|
||||
public function setDestinationPlugin(MigrateDestinationInterface $destination_plugin) {
|
||||
$this->destinationPlugin = $destination_plugin;
|
||||
}
|
||||
|
||||
public function setEntityManager(EntityManagerInterface $entity_manager) {
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface RequirementsAwareSourceInterface extends MigrateSourceInterface, RequirementsInterface {}
|
||||
|
||||
interface RequirementsAwareDestinationInterface extends MigrateDestinationInterface, RequirementsInterface {}
|
250
core/modules/migrate/tests/src/Unit/RowTest.php
Normal file
250
core/modules/migrate/tests/src/Unit/RowTest.php
Normal file
|
@ -0,0 +1,250 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\RowTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Row
|
||||
* @group migrate
|
||||
*/
|
||||
class RowTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The source IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $testSourceIds = array(
|
||||
'nid' => 'Node ID',
|
||||
);
|
||||
|
||||
/**
|
||||
* The test values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $testValues = array(
|
||||
'nid' => 1,
|
||||
'title' => 'node 1',
|
||||
);
|
||||
|
||||
/**
|
||||
* The test hash.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testHash = '85795d4cde4a2425868b812cc88052ecd14fc912e7b9b4de45780f66750e8b1e';
|
||||
|
||||
/**
|
||||
* The test hash after changing title value to 'new title'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testHashMod = '9476aab0b62b3f47342cc6530441432e5612dcba7ca84115bbab5cceaca1ecb3';
|
||||
|
||||
/**
|
||||
* Tests object creation: empty.
|
||||
*/
|
||||
public function testRowWithoutData() {
|
||||
$row = new Row(array(), array());
|
||||
$this->assertSame(array(), $row->getSource(), 'Empty row');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests object creation: basic.
|
||||
*/
|
||||
public function testRowWithBasicData() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$this->assertSame($this->testValues, $row->getSource(), 'Row with data, simple id.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests object creation: multiple source IDs.
|
||||
*/
|
||||
public function testRowWithMultipleSourceIds() {
|
||||
$multi_source_ids = $this->testSourceIds + array('vid' => 'Node revision');
|
||||
$multi_source_ids_values = $this->testValues + array('vid' => 1);
|
||||
$row = new Row($multi_source_ids_values, $multi_source_ids);
|
||||
$this->assertSame($multi_source_ids_values, $row->getSource(), 'Row with data, multifield id.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests object creation: invalid values.
|
||||
*
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testRowWithInvalidData() {
|
||||
$invalid_values = array(
|
||||
'title' => 'node X',
|
||||
);
|
||||
$row = new Row($invalid_values, $this->testSourceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests source immutability after freeze.
|
||||
*
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testSourceFreeze() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$row->rehash();
|
||||
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash.');
|
||||
$row->setSourceProperty('title', 'new title');
|
||||
$row->rehash();
|
||||
$this->assertSame($this->testHashMod, $row->getHash(), 'Hash changed correctly.');
|
||||
$row->freezeSource();
|
||||
$row->setSourceProperty('title', 'new title');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting on a frozen row.
|
||||
*
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage The source is frozen and can't be changed any more
|
||||
*/
|
||||
public function testSetFrozenRow() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$row->freezeSource();
|
||||
$row->setSourceProperty('title', 'new title');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hashing.
|
||||
*/
|
||||
public function testHashing() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$this->assertSame('', $row->getHash(), 'No hash at creation');
|
||||
$row->rehash();
|
||||
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash.');
|
||||
$row->rehash();
|
||||
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash even doing it twice.');
|
||||
|
||||
// Set the map to needs update.
|
||||
$test_id_map = array(
|
||||
'original_hash' => '',
|
||||
'hash' => '',
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
);
|
||||
$row->setIdMap($test_id_map);
|
||||
$this->assertTrue($row->needsUpdate());
|
||||
|
||||
$row->rehash();
|
||||
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash even if id_mpa have changed.');
|
||||
$row->setSourceProperty('title', 'new title');
|
||||
$row->rehash();
|
||||
$this->assertSame($this->testHashMod, $row->getHash(), 'Hash changed correctly.');
|
||||
// Check hash calculation algorithm.
|
||||
$hash = hash('sha256', serialize($row->getSource()));
|
||||
$this->assertSame($hash, $row->getHash());
|
||||
// Check length of generated hash used for mapping schema.
|
||||
$this->assertSame(64, strlen($row->getHash()));
|
||||
|
||||
// Set the map to successfully imported.
|
||||
$test_id_map = array(
|
||||
'original_hash' => '',
|
||||
'hash' => '',
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
);
|
||||
$row->setIdMap($test_id_map);
|
||||
$this->assertFalse($row->needsUpdate());
|
||||
|
||||
// Set the same hash value and ensure it was not changed.
|
||||
$random = $this->randomMachineName();
|
||||
$test_id_map = array(
|
||||
'original_hash' => $random,
|
||||
'hash' => $random,
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
);
|
||||
$row->setIdMap($test_id_map);
|
||||
$this->assertFalse($row->changed());
|
||||
|
||||
// Set different has values to ensure it is marked as changed.
|
||||
$test_id_map = array(
|
||||
'original_hash' => $this->randomMachineName(),
|
||||
'hash' => $this->randomMachineName(),
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
);
|
||||
$row->setIdMap($test_id_map);
|
||||
$this->assertTrue($row->changed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting/setting the ID Map.
|
||||
*
|
||||
* @covers ::setIdMap
|
||||
* @covers ::getIdMap
|
||||
*/
|
||||
public function testGetSetIdMap() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$test_id_map = array(
|
||||
'original_hash' => '',
|
||||
'hash' => '',
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
);
|
||||
$row->setIdMap($test_id_map);
|
||||
$this->assertEquals($test_id_map, $row->getIdMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the source ID.
|
||||
*/
|
||||
public function testSourceIdValues() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$this->assertSame(array('nid' => $this->testValues['nid']), $row->getSourceIdValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting the source property.
|
||||
*
|
||||
* @covers ::getSourceProperty
|
||||
*/
|
||||
public function testGetSourceProperty() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$this->assertSame($this->testValues['nid'], $row->getSourceProperty('nid'));
|
||||
$this->assertSame($this->testValues['title'], $row->getSourceProperty('title'));
|
||||
$this->assertNull($row->getSourceProperty('non_existing'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting and getting the destination.
|
||||
*/
|
||||
public function testDestination() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
$this->assertEmpty($row->getDestination());
|
||||
$this->assertFalse($row->hasDestinationProperty('nid'));
|
||||
|
||||
// Set a destination.
|
||||
$row->setDestinationProperty('nid', 2);
|
||||
$this->assertTrue($row->hasDestinationProperty('nid'));
|
||||
$this->assertEquals(array('nid' => 2), $row->getDestination());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting/getting multiple destination IDs.
|
||||
*/
|
||||
public function testMultipleDestination() {
|
||||
$row = new Row($this->testValues, $this->testSourceIds);
|
||||
// Set some deep nested values.
|
||||
$row->setDestinationProperty('image/alt', 'alt text');
|
||||
$row->setDestinationProperty('image/fid', 3);
|
||||
|
||||
$this->assertTrue($row->hasDestinationProperty('image'));
|
||||
$this->assertFalse($row->hasDestinationProperty('alt'));
|
||||
$this->assertFalse($row->hasDestinationProperty('fid'));
|
||||
|
||||
$destination = $row->getDestination();
|
||||
$this->assertEquals('alt text', $destination['image']['alt']);
|
||||
$this->assertEquals(3, $destination['image']['fid']);
|
||||
$this->assertEquals('alt text', $row->getDestinationProperty('image/alt'));
|
||||
$this->assertEquals(3, $row->getDestinationProperty('image/fid'));
|
||||
}
|
||||
|
||||
}
|
177
core/modules/migrate/tests/src/Unit/SqlBaseTest.php
Normal file
177
core/modules/migrate/tests/src/Unit/SqlBaseTest.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\SqlBaseTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\source\SqlBase;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Tests the SqlBase class.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class SqlBaseTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @param bool $expected_result
|
||||
* The expected result.
|
||||
* @param bool $id_map_is_sql
|
||||
* TRUE if we want getIdMap() to return an instance of Sql.
|
||||
* @param bool $with_id_map
|
||||
* TRUE if we want the id map to have a valid map of ids.
|
||||
* @param array $source_options
|
||||
* An array of connection options for the source connection.
|
||||
* @param array $idmap_options
|
||||
* An array of connection options for the id map connection.
|
||||
*
|
||||
* @dataProvider sqlBaseTestProvider
|
||||
*/
|
||||
public function testMapJoinable($expected_result, $id_map_is_sql, $with_id_map, $source_options = [], $idmap_options = []) {
|
||||
// Setup a connection object.
|
||||
$source_connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$source_connection->expects($id_map_is_sql && $with_id_map ? $this->once() : $this->never())
|
||||
->method('getConnectionOptions')
|
||||
->willReturn($source_options);
|
||||
|
||||
// Setup the id map connection.
|
||||
$idmap_connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$idmap_connection->expects($id_map_is_sql && $with_id_map ? $this->once() : $this->never())
|
||||
->method('getConnectionOptions')
|
||||
->willReturn($idmap_options);
|
||||
|
||||
// Setup the Sql object.
|
||||
$sql = $this->getMockBuilder('Drupal\migrate\Plugin\migrate\id_map\Sql')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$sql->expects($id_map_is_sql && $with_id_map ? $this->once() : $this->never())
|
||||
->method('getDatabase')
|
||||
->willReturn($idmap_connection);
|
||||
|
||||
// Setup a migration entity.
|
||||
$migration = $this->getMock('Drupal\migrate\Entity\MigrationInterface');
|
||||
$migration->expects($with_id_map ? $this->once() : $this->never())
|
||||
->method('getIdMap')
|
||||
->willReturn($id_map_is_sql ? $sql : NULL);
|
||||
|
||||
// Create our SqlBase test class.
|
||||
$sql_base = new TestSqlBase();
|
||||
$sql_base->setMigration($migration);
|
||||
$sql_base->setDatabase($source_connection);
|
||||
|
||||
// Configure the idMap to make the check in mapJoinable() pass.
|
||||
if ($with_id_map) {
|
||||
$sql_base->setIds([
|
||||
'uid' => ['type' => 'integer', 'alias' => 'u'],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->assertEquals($expected_result, $sql_base->mapJoinable());
|
||||
}
|
||||
|
||||
/**
|
||||
* The data provider for SqlBase.
|
||||
*
|
||||
* @return array
|
||||
* An array of data per test run.
|
||||
*/
|
||||
public function sqlBaseTestProvider() {
|
||||
return [
|
||||
// Source ids are empty so mapJoinable() is false.
|
||||
[FALSE, FALSE, FALSE],
|
||||
// Still false because getIdMap() is not a subclass of Sql.
|
||||
[FALSE, FALSE, TRUE],
|
||||
// Test mapJoinable() returns false when source and id connection options
|
||||
// differ.
|
||||
[FALSE, TRUE, TRUE, ['username' => 'different_from_map', 'password' => 'different_from_map'], ['username' => 'different_from_source', 'password' => 'different_from_source']],
|
||||
// Returns true because source and id map connection options are the same.
|
||||
[TRUE, TRUE, TRUE, ['username' => 'same_value', 'password' => 'same_value'], ['username' => 'same_value', 'password' => 'same_value']],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestSqlBase extends SqlBase {
|
||||
|
||||
protected $database;
|
||||
protected $ids;
|
||||
|
||||
/**
|
||||
* Override the constructor so we can create one easily.
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Allows us to set the database during tests.
|
||||
*
|
||||
* @param mixed $database
|
||||
* The database mock object.
|
||||
*/
|
||||
public function setDatabase($database) {
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDatabase() {
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows us to set the migration during the test.
|
||||
*
|
||||
* @param mixed $migration
|
||||
* The migration mock.
|
||||
*/
|
||||
public function setMigration($migration) {
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mapJoinable() {
|
||||
return parent::mapJoinable();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return $this->ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows us to set the ids during a test.
|
||||
*/
|
||||
public function setIds($ids) {
|
||||
$this->ids = $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
251
core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
Normal file
251
core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
Normal file
|
@ -0,0 +1,251 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\TestMigrateExecutable.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
|
||||
/**
|
||||
* Tests MigrateExecutable.
|
||||
*/
|
||||
class TestMigrateExecutable extends MigrateExecutable {
|
||||
|
||||
/**
|
||||
* The (fake) number of seconds elapsed since the start of the test.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timeElapsed;
|
||||
|
||||
/**
|
||||
* The fake memory usage in bytes.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $memoryUsage;
|
||||
|
||||
/**
|
||||
* The cleared memory usage.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $clearedMemoryUsage;
|
||||
|
||||
/**
|
||||
* Sets the string translation service.
|
||||
*
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The translation manager.
|
||||
*/
|
||||
public function setStringTranslation(TranslationInterface $string_translation) {
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected timeOptionExceeded method.
|
||||
*
|
||||
* @return bool
|
||||
* A threshold exceeded value.
|
||||
*/
|
||||
public function timeOptionExceeded() {
|
||||
return parent::timeOptionExceeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to set protected maxExecTime property.
|
||||
*
|
||||
* @param int $max_exec_time
|
||||
* The value to set.
|
||||
*/
|
||||
public function setMaxExecTime($max_exec_time) {
|
||||
$this->maxExecTime = $max_exec_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected maxExecTime property.
|
||||
*
|
||||
* @return int
|
||||
* The value of the protected property.
|
||||
*/
|
||||
public function getMaxExecTime() {
|
||||
return $this->maxExecTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected successesSinceFeedback property.
|
||||
*
|
||||
* @return int
|
||||
* The value of the protected property.
|
||||
*/
|
||||
public function getSuccessesSinceFeedback() {
|
||||
return $this->successesSinceFeedback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected totalSuccesses property.
|
||||
*
|
||||
* @return int
|
||||
* The value of the protected property.
|
||||
*/
|
||||
public function getTotalSuccesses() {
|
||||
return $this->totalSuccesses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected totalProcessed property.
|
||||
*
|
||||
* @return int
|
||||
* The value of the protected property.
|
||||
*/
|
||||
public function getTotalProcessed() {
|
||||
return $this->totalProcessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected processedSinceFeedback property.
|
||||
*
|
||||
* @return int
|
||||
* The value of the protected property.
|
||||
*/
|
||||
public function getProcessedSinceFeedback() {
|
||||
return $this->processedSinceFeedback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected maxExecTimeExceeded method.
|
||||
*
|
||||
* @return bool
|
||||
* The threshold exceeded value.
|
||||
*/
|
||||
public function maxExecTimeExceeded() {
|
||||
return parent::maxExecTimeExceeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to set protected source property.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrateSourceInterface $source
|
||||
* The value to set.
|
||||
*/
|
||||
public function setSource($source) {
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to protected sourceIdValues property.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The value to set.
|
||||
*/
|
||||
public function setSourceIdValues($source_id_values) {
|
||||
$this->sourceIdValues = $source_id_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows setting a fake elapsed time.
|
||||
*
|
||||
* @param int $time
|
||||
* The time in seconds.
|
||||
*/
|
||||
public function setTimeElapsed($time) {
|
||||
$this->timeElapsed = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTimeElapsed() {
|
||||
return $this->timeElapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleException(\Exception $exception, $save = TRUE) {
|
||||
$message = $exception->getMessage();
|
||||
if ($save) {
|
||||
$this->saveMessage($message);
|
||||
}
|
||||
$this->message->display($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to the protected memoryExceeded method.
|
||||
*
|
||||
* @return bool
|
||||
* The memoryExceeded value.
|
||||
*/
|
||||
public function memoryExceeded() {
|
||||
return parent::memoryExceeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function attemptMemoryReclaim() {
|
||||
return $this->clearedMemoryUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getMemoryUsage() {
|
||||
return $this->memoryUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fake memory usage.
|
||||
*
|
||||
* @param int $memory_usage
|
||||
* The fake memory usage value.
|
||||
* @param int $cleared_memory_usage
|
||||
* (optional) The fake cleared memory value.
|
||||
*/
|
||||
public function setMemoryUsage($memory_usage, $cleared_memory_usage = NULL) {
|
||||
$this->memoryUsage = $memory_usage;
|
||||
$this->clearedMemoryUsage = $cleared_memory_usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the memory limit.
|
||||
*
|
||||
* @param int $memory_limit
|
||||
* The memory limit.
|
||||
*/
|
||||
public function setMemoryLimit($memory_limit) {
|
||||
$this->memoryLimit = $memory_limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the memory threshold.
|
||||
*
|
||||
* @param float $threshold
|
||||
* The new threshold.
|
||||
*/
|
||||
public function setMemoryThreshold($threshold) {
|
||||
$this->memoryThreshold = $threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time threshold.
|
||||
*
|
||||
* @param float $threshold
|
||||
* The new threshold.
|
||||
*/
|
||||
public function setTimeThreshold($threshold) {
|
||||
$this->timeThreshold = $threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function formatSize($size) {
|
||||
return $size;
|
||||
}
|
||||
|
||||
}
|
67
core/modules/migrate/tests/src/Unit/TestSqlIdMap.php
Normal file
67
core/modules/migrate/tests/src/Unit/TestSqlIdMap.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\TestSqlIdMap.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\migrate\id_map\Sql;
|
||||
|
||||
/**
|
||||
* Defines a SQL ID map for use in tests.
|
||||
*/
|
||||
class TestSqlIdMap extends Sql implements \Iterator {
|
||||
|
||||
/**
|
||||
* Constructs a TestSqlIdMap object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database.
|
||||
* @param array $configuration
|
||||
* The configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the migration process to do.
|
||||
* @param mixed $plugin_definition
|
||||
* The configuration for the plugin.
|
||||
* @param \Drupal\migrate\Entity\MigrationInterface $migration
|
||||
* The migration to do.
|
||||
*/
|
||||
public function __construct(Connection $database, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||
$this->database = $database;
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDatabase() {
|
||||
return parent::getDatabase();
|
||||
}
|
||||
|
||||
protected function getFieldSchema(array $id_definition) {
|
||||
if (!isset($id_definition['type'])) {
|
||||
return array();
|
||||
}
|
||||
switch ($id_definition['type']) {
|
||||
case 'integer':
|
||||
return array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
);
|
||||
case 'string':
|
||||
return array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
);
|
||||
default:
|
||||
throw new MigrateException(SafeMarkup::format('@type not supported', array('@type' => $id_definition['type'])));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\destination\ConfigTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\destination;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\destination\Config;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\destination\Config
|
||||
* @group migrate
|
||||
*/
|
||||
class ConfigTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Test the import method.
|
||||
*/
|
||||
public function testImport() {
|
||||
$source = array(
|
||||
'test' => 'x',
|
||||
);
|
||||
$migration = $this->getMockBuilder('Drupal\migrate\Entity\Migration')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$config = $this->getMockBuilder('Drupal\Core\Config\Config')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
foreach ($source as $key => $val) {
|
||||
$config->expects($this->once())
|
||||
->method('set')
|
||||
->with($this->equalTo($key), $this->equalTo($val))
|
||||
->will($this->returnValue($config));
|
||||
}
|
||||
$config->expects($this->once())
|
||||
->method('save');
|
||||
$config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface');
|
||||
$config_factory->expects($this->once())
|
||||
->method('getEditable')
|
||||
->with('d8_config')
|
||||
->will($this->returnValue($config));
|
||||
$row = $this->getMockBuilder('Drupal\migrate\Row')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$row->expects($this->once())
|
||||
->method('getRawDestination')
|
||||
->will($this->returnValue($source));
|
||||
$destination = new Config(array('config_name' => 'd8_config'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory);
|
||||
$destination->import($row);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue