non security modules update
This commit is contained in:
@@ -37,9 +37,9 @@ abstract class MigrationBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of a migration group, used to collect related migrations.
|
||||
* A migration group object, used to collect related migrations.
|
||||
*
|
||||
* @var string
|
||||
* @var MigrateGroup
|
||||
*/
|
||||
protected $group;
|
||||
public function getGroup() {
|
||||
@@ -55,6 +55,9 @@ abstract class MigrationBase {
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save options passed to current operation
|
||||
@@ -143,9 +146,21 @@ abstract class MigrationBase {
|
||||
public function getHardDependencies() {
|
||||
return $this->dependencies;
|
||||
}
|
||||
public function setHardDependencies(array $dependencies) {
|
||||
$this->dependencies = $dependencies;
|
||||
}
|
||||
public function addHardDependencies(array $dependencies) {
|
||||
$this->dependencies = array_merge($this->dependencies, $dependencies);
|
||||
}
|
||||
public function getSoftDependencies() {
|
||||
return $this->softDependencies;
|
||||
}
|
||||
public function setSoftDependencies(array $dependencies) {
|
||||
$this->softDependencies = $dependencies;
|
||||
}
|
||||
public function addSoftDependencies(array $dependencies) {
|
||||
$this->softDependencies = array_merge($this->softDependencies, $dependencies);
|
||||
}
|
||||
public function getDependencies() {
|
||||
return array_merge($this->dependencies, $this->softDependencies);
|
||||
}
|
||||
@@ -161,6 +176,13 @@ abstract class MigrationBase {
|
||||
self::$displayFunction = $display_function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track whether or not we've already displayed an encryption warning
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $showEncryptionWarning = TRUE;
|
||||
|
||||
/**
|
||||
* The fraction of the memory limit at which an operation will be interrupted.
|
||||
* Can be overridden by a Migration subclass if one would like to push the
|
||||
@@ -193,6 +215,14 @@ abstract class MigrationBase {
|
||||
*/
|
||||
protected $timeLimit;
|
||||
|
||||
/**
|
||||
* A time limit in seconds appropriate to be used in a batch
|
||||
* import. Defaults to 240.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $batchTimeLimit = 240;
|
||||
|
||||
/**
|
||||
* MigrateTeamMember objects representing people involved with this
|
||||
* migration.
|
||||
@@ -203,6 +233,9 @@ abstract class MigrationBase {
|
||||
public function getTeam() {
|
||||
return $this->team;
|
||||
}
|
||||
public function setTeam(array $team) {
|
||||
$this->team = $team;
|
||||
}
|
||||
|
||||
/**
|
||||
* If provided, an URL for an issue tracking system containing :id where
|
||||
@@ -214,6 +247,9 @@ abstract class MigrationBase {
|
||||
public function getIssuePattern() {
|
||||
return $this->issuePattern;
|
||||
}
|
||||
public function setIssuePattern($issue_pattern) {
|
||||
$this->issuePattern = $issue_pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we set an error handler (during import), remember the previous one so
|
||||
@@ -223,6 +259,22 @@ abstract class MigrationBase {
|
||||
*/
|
||||
protected $previousErrorHandler = NULL;
|
||||
|
||||
/**
|
||||
* Arguments configuring a migration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = array();
|
||||
public function getArguments() {
|
||||
return $this->arguments;
|
||||
}
|
||||
public function setArguments(array $arguments) {
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
public function addArguments(array $arguments) {
|
||||
$this->arguments = array_merge($this->arguments, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabling a migration prevents it from running with --all, or individually
|
||||
* without --force
|
||||
@@ -233,6 +285,29 @@ abstract class MigrationBase {
|
||||
public function getEnabled() {
|
||||
return $this->enabled;
|
||||
}
|
||||
public function setEnabled($enabled) {
|
||||
$this->enabled = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any module hooks which should be disabled during migration processes.
|
||||
*
|
||||
* @var array
|
||||
* Key: Hook name (e.g., 'node_insert')
|
||||
* Value: Array of modules for which to disable this hook (e.g., array('pathauto')).
|
||||
*/
|
||||
protected $disableHooks = array();
|
||||
public function getDisableHooks() {
|
||||
return $this->disableHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Have we already warned about obsolete constructor argumentss on this request?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
static protected $groupArgumentWarning = FALSE;
|
||||
static protected $emptyArgumentsWarning = FALSE;
|
||||
|
||||
/**
|
||||
* Codes representing the result of a rollback or import process.
|
||||
@@ -284,17 +359,51 @@ abstract class MigrationBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* General initialization of a MigrationBase object.
|
||||
* Construction of a MigrationBase instance.
|
||||
*
|
||||
* @param array $arguments
|
||||
*/
|
||||
public function __construct($group = NULL) {
|
||||
$this->machineName = $this->generateMachineName();
|
||||
|
||||
if (empty($group)) {
|
||||
$this->group = MigrateGroup::getInstance('default');
|
||||
public function __construct($arguments = array()) {
|
||||
// Support for legacy code passing a group object as the first parameter.
|
||||
if (is_object($arguments) && is_a($arguments, 'MigrateGroup')) {
|
||||
$this->group = $arguments;
|
||||
$this->arguments['group_name'] = $arguments->getName();
|
||||
if (!self::$groupArgumentWarning &&
|
||||
variable_get('migrate_deprecation_warnings', 1)) {
|
||||
self::displayMessage(t('Passing a group object to a migration constructor is now deprecated - pass through the arguments array passed to the leaf class instead.'));
|
||||
self::$groupArgumentWarning = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->group = $group;
|
||||
if (empty($arguments)) {
|
||||
$this->arguments = array();
|
||||
if (!self::$emptyArgumentsWarning &&
|
||||
variable_get('migrate_deprecation_warnings', 1)) {
|
||||
self::displayMessage(t('Passing an empty first parameter to a migration constructor is now deprecated - pass through the arguments array passed to the leaf class instead.'));
|
||||
self::$emptyArgumentsWarning = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
if (empty($this->arguments['group_name'])) {
|
||||
$this->arguments['group_name'] = 'default';
|
||||
}
|
||||
$this->group = MigrateGroup::getInstance($this->arguments['group_name']);
|
||||
}
|
||||
if (isset($this->arguments['machine_name'])) {
|
||||
$this->machineName = $this->arguments['machine_name'];
|
||||
}
|
||||
else {
|
||||
// Deprecated - this supports old code which does not pass the arguments
|
||||
// array through to the base constructor. Remove in the next version.
|
||||
$this->machineName = $this->machineFromClass(get_class($this));
|
||||
}
|
||||
// Make any group arguments directly accessible to the specific migration,
|
||||
// other than group dependencies.
|
||||
$group_arguments = $this->group->getArguments();
|
||||
unset($group_arguments['dependencies']);
|
||||
$this->arguments += $group_arguments;
|
||||
|
||||
// Record the memory limit in bytes
|
||||
$limit = trim(ini_get('memory_limit'));
|
||||
@@ -330,9 +439,18 @@ abstract class MigrationBase {
|
||||
$conf['mail_system'][$system] = 'MigrateMailIgnore';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$conf['mail_system']['default-system'] = 'MigrateMailIgnore';
|
||||
}
|
||||
|
||||
// Make sure we clear our semaphores in case of abrupt exit
|
||||
register_shutdown_function(array($this, 'endProcess'));
|
||||
drupal_register_shutdown_function(array($this, 'endProcess'));
|
||||
|
||||
// Save any hook disablement information.
|
||||
if (isset($this->arguments['disable_hooks']) &&
|
||||
is_array($this->arguments['disable_hooks'])) {
|
||||
$this->disableHooks = $this->arguments['disable_hooks'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,7 +476,10 @@ abstract class MigrationBase {
|
||||
* @param string $machine_name
|
||||
* @param array $arguments
|
||||
*/
|
||||
static public function registerMigration($class_name, $machine_name = NULL, array $arguments = array()) {
|
||||
static public function registerMigration($class_name, $machine_name = NULL,
|
||||
array $arguments = array()) {
|
||||
// Support for legacy migration code - in later releases, the machine_name
|
||||
// should always be explicit.
|
||||
if (!$machine_name) {
|
||||
$machine_name = self::machineFromClass($class_name);
|
||||
}
|
||||
@@ -368,18 +489,28 @@ abstract class MigrationBase {
|
||||
array('!name' => $machine_name)));
|
||||
}
|
||||
|
||||
// Making sure the machine name is in the arguments array helps with
|
||||
// chicken-and-egg problems in determining the machine name.
|
||||
if (!isset($arguments['machine_name'])) {
|
||||
$arguments['machine_name'] = $machine_name;
|
||||
// We no longer have any need to store the machine_name in the arguments.
|
||||
if (isset($arguments['machine_name'])) {
|
||||
unset($arguments['machine_name']);
|
||||
}
|
||||
|
||||
if (isset($arguments['group_name'])) {
|
||||
$group_name = $arguments['group_name'];
|
||||
unset($arguments['group_name']);
|
||||
}
|
||||
else {
|
||||
$group_name = 'default';
|
||||
}
|
||||
|
||||
$arguments = self::encryptArguments($arguments);
|
||||
|
||||
// Register the migration if it's not already there; if it is,
|
||||
// update the class and arguments in case they've changed.
|
||||
db_merge('migrate_status')
|
||||
->key(array('machine_name' => $machine_name))
|
||||
->fields(array(
|
||||
'class_name' => $class_name,
|
||||
'group_name' => $group_name,
|
||||
'arguments' => serialize($arguments)
|
||||
))
|
||||
->execute();
|
||||
@@ -395,17 +526,27 @@ abstract class MigrationBase {
|
||||
$rows_deleted = db_delete('migrate_status')
|
||||
->condition('machine_name', $machine_name)
|
||||
->execute();
|
||||
// Make sure the group gets deleted if we were the only member.
|
||||
MigrateGroup::deleteOrphans();
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, the migration machine name is the class name (with the
|
||||
* Migration suffix, if present, stripped).
|
||||
* The migration machine name is stored in the arguments.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateMachineName() {
|
||||
$class_name = get_class($this);
|
||||
return self::machineFromClass($class_name);
|
||||
return $this->arguments['machine_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given only a class name, derive a machine name (the class name with the
|
||||
* "Migration" suffix, if any, removed).
|
||||
*
|
||||
* @param $class_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function machineFromClass($class_name) {
|
||||
if (preg_match('/Migration$/', $class_name)) {
|
||||
$machine_name = drupal_substr($class_name, 0,
|
||||
@@ -422,38 +563,74 @@ abstract class MigrationBase {
|
||||
*
|
||||
* @param string $machine_name
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return the single instance of the given migration.
|
||||
*
|
||||
* @param $machine_name
|
||||
* The unique machine name of the migration to retrieve.
|
||||
* @param string $class_name
|
||||
* Deprecated - no longer used, class name is retrieved from migrate_status.
|
||||
* @param array $arguments
|
||||
* Deprecated - no longer used, arguments are retrieved from migrate_status.
|
||||
*
|
||||
* @return MigrationBase
|
||||
*/
|
||||
static public function getInstance($machine_name, $class_name = NULL, array $arguments = array()) {
|
||||
$migrations = &drupal_static(__FUNCTION__, array());
|
||||
// Otherwise might miss cache hit on case difference
|
||||
$machine_name_key = drupal_strtolower($machine_name);
|
||||
if (!isset($migrations[$machine_name_key])) {
|
||||
// Skip the query if our caller already made it
|
||||
if (!$class_name) {
|
||||
// See if we know about this migration
|
||||
$row = db_select('migrate_status', 'ms')
|
||||
->fields('ms', array('class_name', 'arguments'))
|
||||
->condition('machine_name', $machine_name)
|
||||
->execute()
|
||||
->fetchObject();
|
||||
if ($row) {
|
||||
$class_name = $row->class_name;
|
||||
$arguments = unserialize($row->arguments);
|
||||
// See if we know about this migration
|
||||
$row = db_select('migrate_status', 'ms')
|
||||
->fields('ms', array('class_name', 'group_name', 'arguments'))
|
||||
->condition('machine_name', $machine_name)
|
||||
->execute()
|
||||
->fetchObject();
|
||||
if ($row) {
|
||||
$class_name = $row->class_name;
|
||||
$arguments = unserialize($row->arguments);
|
||||
$arguments = self::decryptArguments($arguments);
|
||||
$arguments['group_name'] = $row->group_name;
|
||||
}
|
||||
else {
|
||||
// Can't find a migration with this name
|
||||
self::displayMessage(t('No migration found with machine name !machine',
|
||||
array('!machine' => $machine_name)));
|
||||
return NULL;
|
||||
}
|
||||
$arguments['machine_name'] = $machine_name;
|
||||
if (class_exists($class_name)) {
|
||||
try {
|
||||
$migrations[$machine_name_key] = new $class_name($arguments);
|
||||
}
|
||||
else {
|
||||
// Can't find a migration with this name
|
||||
throw new MigrateException(t('No migration found with machine name !machine',
|
||||
catch (Exception $e) {
|
||||
self::displayMessage(t('Migration !machine could not be constructed.',
|
||||
array('!machine' => $machine_name)));
|
||||
self::displayMessage($e->getMessage());
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
$migrations[$machine_name_key] = new $class_name($arguments);
|
||||
else {
|
||||
self::displayMessage(t('No migration class !class found',
|
||||
array('!class' => $class_name)));
|
||||
return NULL;
|
||||
}
|
||||
if (isset($arguments['dependencies'])) {
|
||||
$migrations[$machine_name_key]->setHardDependencies(
|
||||
$arguments['dependencies']);
|
||||
}
|
||||
if (isset($arguments['soft_dependencies'])) {
|
||||
$migrations[$machine_name_key]->setSoftDependencies(
|
||||
$arguments['soft_dependencies']);
|
||||
}
|
||||
}
|
||||
return $migrations[$machine_name_key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies whether this migration is "dynamic" (that is, allows multiple
|
||||
* instances distinguished by differing parameters). A dynamic class should
|
||||
* override this with a return value of TRUE.
|
||||
* @deprecated - No longer a useful distinction between "status" and "dynamic"
|
||||
* migrations.
|
||||
*/
|
||||
static public function isDynamic() {
|
||||
return FALSE;
|
||||
@@ -625,9 +802,16 @@ abstract class MigrationBase {
|
||||
if (!empty($this->highwaterField['type']) && $this->highwaterField['type'] == 'int') {
|
||||
// If the highwater is an integer type, we need to force the DB server
|
||||
// to treat the varchar highwater field as an integer (otherwise it will
|
||||
// think '5' > '10'). CAST(highwater AS INTEGER) would be ideal, but won't
|
||||
// work in MySQL. This hack is thought to be portable.
|
||||
$query->where('(highwater+0) < :highwater', array(':highwater' => $highwater));
|
||||
// think '5' > '10').
|
||||
switch (Database::getConnection()->databaseType()) {
|
||||
case 'pgsql':
|
||||
$query->where('(CASE WHEN highwater=\'\' THEN 0 ELSE CAST(highwater AS INTEGER) END) < :highwater', array(':highwater' => intval($highwater)));
|
||||
break;
|
||||
default:
|
||||
// CAST(highwater AS INTEGER) would be ideal, but won't
|
||||
// work in MySQL. This hack is thought to be portable.
|
||||
$query->where('(highwater+0) < :highwater', array(':highwater' => $highwater));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$query->condition('highwater', $highwater, '<');
|
||||
@@ -684,7 +868,7 @@ abstract class MigrationBase {
|
||||
else {
|
||||
foreach ($this->dependencies as $dependency) {
|
||||
$migration = MigrationBase::getInstance($dependency);
|
||||
if (!$migration->isComplete()) {
|
||||
if (!$migration || !$migration->isComplete()) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -699,7 +883,7 @@ abstract class MigrationBase {
|
||||
$incomplete = array();
|
||||
foreach ($this->getDependencies() as $dependency) {
|
||||
$migration = MigrationBase::getInstance($dependency);
|
||||
if (!$migration->isComplete()) {
|
||||
if (!$migration || !$migration->isComplete()) {
|
||||
$incomplete[] = $dependency;
|
||||
}
|
||||
}
|
||||
@@ -752,6 +936,17 @@ abstract class MigrationBase {
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
// If we're disabling any hooks, reset the static module_implements cache so
|
||||
// it is rebuilt with the specified hooks removed by our
|
||||
// hook_module_implements_alter(). By setting #write_cache to FALSE, we
|
||||
// ensure that our munged version of the hooks array does not get written
|
||||
// to the persistent cache and interfere with other Drupal processes.
|
||||
if (!empty($this->disableHooks)) {
|
||||
$implementations = &drupal_static('module_implements');
|
||||
$implementations = array();
|
||||
$implementations['#write_cache'] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -904,6 +1099,14 @@ abstract class MigrationBase {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PHP time limit. This method may be called from batch callbacks
|
||||
* before calling the processImport method.
|
||||
*/
|
||||
public function setBatchTimeLimit() {
|
||||
drupal_set_time_limit($this->batchTimeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* A derived migration class does the actual rollback or import work in these
|
||||
* methods - we cannot declare them abstract because some classes may define
|
||||
@@ -999,6 +1202,132 @@ abstract class MigrationBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt an incoming value. Detects for existence of the Drupal 'Encrypt'
|
||||
* module or the mcrypt PHP extension.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string The encrypted value.
|
||||
*/
|
||||
static public function encrypt($value) {
|
||||
if (module_exists('encrypt')) {
|
||||
$value = encrypt($value);
|
||||
}
|
||||
else if (extension_loaded('mcrypt')) {
|
||||
// Mimic encrypt module to ensure compatibility
|
||||
$key = drupal_substr(variable_get('drupal_private_key', 'no_key'), 0, 32);
|
||||
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
|
||||
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
|
||||
$value = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $value,
|
||||
MCRYPT_MODE_ECB, $iv);
|
||||
|
||||
$encryption_array['text'] = $value;
|
||||
// For forward compatibility with the encrypt module.
|
||||
$encryption_array['method'] = 'mcrypt_rij_256';
|
||||
$encryption_array['key_name'] = 'drupal_private_key';
|
||||
$value = serialize($encryption_array);
|
||||
}
|
||||
else {
|
||||
if (self::$showEncryptionWarning) {
|
||||
MigrationBase::displayMessage(t('Encryption of secure migration information is not supported. Ensure the <a href="@encrypt">Encrypt module</a> or <a href="mcrypt">mcrypt PHP extension</a> is installed for this functionality.',
|
||||
array(
|
||||
'@encrypt' => 'http://drupal.org/project/encrypt',
|
||||
'@mcrypt' => 'http://php.net/manual/en/book.mcrypt.php',
|
||||
)
|
||||
),
|
||||
'warning');
|
||||
self::$showEncryptionWarning = FALSE;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an incoming value.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string The encrypted value
|
||||
*/
|
||||
static public function decrypt($value) {
|
||||
if (module_exists('encrypt')) {
|
||||
$value = decrypt($value);
|
||||
}
|
||||
else if (extension_loaded('mcrypt')) {
|
||||
// Mimic encrypt module to ensure compatibility
|
||||
$encryption_array = unserialize($value);
|
||||
$method = $encryption_array['method']; // Not used right now
|
||||
$text = $encryption_array['text'];
|
||||
$key_name = $encryption_array['key_name']; // Not used right now
|
||||
|
||||
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
|
||||
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
|
||||
$key = drupal_substr(variable_get('drupal_private_key', 'no_key'), 0, 32);
|
||||
$value = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $text,
|
||||
MCRYPT_MODE_ECB, $iv);
|
||||
}
|
||||
else {
|
||||
if (self::$showEncryptionWarning) {
|
||||
MigrationBase::displayMessage(t('Encryption of secure migration information is not supported. Ensure the <a href="@encrypt">Encrypt module</a> or <a href="mcrypt">mcrypt PHP extension</a> is installed for this functionality.',
|
||||
array(
|
||||
'@encrypt' => 'http://drupal.org/project/encrypt',
|
||||
'@mcrypt' => 'http://php.net/manual/en/book.mcrypt.php',
|
||||
)
|
||||
),
|
||||
'warning');
|
||||
self::$showEncryptionWarning = FALSE;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure any arguments we want to be encrypted get encrypted.
|
||||
*
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function encryptArguments(array $arguments) {
|
||||
if (isset($arguments['encrypted_arguments'])) {
|
||||
foreach ($arguments['encrypted_arguments'] as $argument_name) {
|
||||
if (isset($arguments[$argument_name])) {
|
||||
$arguments[$argument_name] = self::encrypt(
|
||||
serialize($arguments[$argument_name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure any arguments we want to be decrypted get decrypted.
|
||||
*
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function decryptArguments(array $arguments) {
|
||||
if (isset($arguments['encrypted_arguments'])) {
|
||||
foreach ($arguments['encrypted_arguments'] as $argument_name) {
|
||||
if (isset($arguments[$argument_name])) {
|
||||
$decrypted_string = self::decrypt($arguments[$argument_name]);
|
||||
// A decryption failure will return FALSE and issue a notice. We need
|
||||
// to distinguish a failure from a serialized FALSE.
|
||||
$unserialized_value = @unserialize($decrypted_string);
|
||||
if ($unserialized_value === FALSE && $decrypted_string != serialize(FALSE)) {
|
||||
self::displayMessage(t('Failed to decrypt argument %argument_name',
|
||||
array('%argument_name' => $argument_name)));
|
||||
unset($arguments[$argument_name]);
|
||||
}
|
||||
else {
|
||||
$arguments[$argument_name] = $unserialized_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an incoming string (which may be a UNIX timestamp, or an arbitrarily-formatted
|
||||
* date/time string) to a UNIX timestamp.
|
||||
@@ -1006,16 +1335,16 @@ abstract class MigrationBase {
|
||||
* @param string $value
|
||||
*/
|
||||
static public function timestamp($value) {
|
||||
// Default empty values to now
|
||||
if (empty($value)) {
|
||||
return time();
|
||||
}
|
||||
|
||||
// Does it look like it's already a timestamp? Just return it
|
||||
if (is_numeric($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Default empty values to now
|
||||
if (empty($value)) {
|
||||
return time();
|
||||
}
|
||||
|
||||
$date = new DateTime($value);
|
||||
$time = $date->format('U');
|
||||
if ($time == FALSE) {
|
||||
|
@@ -105,11 +105,22 @@ abstract class MigrateDestination {
|
||||
* All destination handlers should be derived from MigrateDestinationHandler
|
||||
*/
|
||||
abstract class MigrateDestinationHandler extends MigrateHandler {
|
||||
// abstract function arguments(...)
|
||||
// Any one or more of these methods may be implemented
|
||||
|
||||
/**
|
||||
* Any one or more of these methods may be implemented
|
||||
* Documentation of any fields added to the destination by this handler.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type (node, user, etc.) for which to list fields.
|
||||
* @param $bundle
|
||||
* The bundle (article, blog, etc.), if any, for which to list fields.
|
||||
* @param Migration $migration
|
||||
* Optionally, the migration providing the context.
|
||||
* @return array
|
||||
* An array keyed by field name, with field descriptions as values.
|
||||
*/
|
||||
//abstract public function fields();
|
||||
//abstract public function fields($entity_type, $bundle, $migration = NULL);
|
||||
|
||||
//abstract public function prepare($entity, stdClass $row);
|
||||
//abstract public function complete($entity, stdClass $row);
|
||||
}
|
||||
|
@@ -6,13 +6,30 @@
|
||||
*/
|
||||
|
||||
class MigrateException extends Exception {
|
||||
/**
|
||||
* The level of the error being reported (a Migration::MESSAGE_* constant)
|
||||
* @var int
|
||||
*/
|
||||
protected $level;
|
||||
public function getLevel() {
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
public function __construct($message, $level = Migration::MESSAGE_ERROR) {
|
||||
/**
|
||||
* The status to record in the map table for the current item (a
|
||||
* MigrateMap::STATUS_* constant)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $status;
|
||||
public function getStatus() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function __construct($message, $level = Migration::MESSAGE_ERROR,
|
||||
$status = MigrateMap::STATUS_FAILED) {
|
||||
$this->level = $level;
|
||||
$this->status = $status;
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,19 @@ class MigrateFieldMapping {
|
||||
return $this->sourceField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const MAPPING_SOURCE_CODE = 1;
|
||||
const MAPPING_SOURCE_DB = 2;
|
||||
protected $mappingSource = self::MAPPING_SOURCE_CODE;
|
||||
public function getMappingSource() {
|
||||
return $this->mappingSource;
|
||||
}
|
||||
public function setMappingSource($mapping_source) {
|
||||
$this->mappingSource = $mapping_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value for simple mappings, when there is no source mapping or the
|
||||
* source field is empty. If both this and the sourceField are omitted, the
|
||||
@@ -96,6 +109,7 @@ class MigrateFieldMapping {
|
||||
* 'source_field' - Name of the source field in the incoming row containing the
|
||||
* value to be assigned
|
||||
* 'default_value' - A constant value to be assigned in the absence of source_field
|
||||
* Deprecated - subfield notation is now preferred.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -173,6 +187,9 @@ class MigrateFieldMapping {
|
||||
}
|
||||
|
||||
public function arguments($arguments) {
|
||||
if (variable_get('migrate_deprecation_warnings', 1)) {
|
||||
MigrationBase::displayMessage(t('The field mapping arguments() method is now deprecated - please use subfield notation instead.'));
|
||||
}
|
||||
$this->arguments = $arguments;
|
||||
return $this;
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
class MigrateGroup {
|
||||
/**
|
||||
* The name of the group - used to identify it in drush commands.
|
||||
* The machine name of the group - used to identify it in drush commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
@@ -16,6 +16,27 @@ class MigrateGroup {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user-visible title of the group.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
public function getTitle() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain-specific arguments for the group (to be applied to all migrations
|
||||
* in the group).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = array();
|
||||
public function getArguments() {
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of groups this group is dependent on.
|
||||
*
|
||||
@@ -35,9 +56,11 @@ class MigrateGroup {
|
||||
static public function groups() {
|
||||
$groups = array();
|
||||
$dependent_groups = array();
|
||||
$dependencies_list = array();
|
||||
$required_groups = array();
|
||||
foreach (self::$groupList as $name => $group) {
|
||||
$dependencies = $group->getDependencies();
|
||||
$dependencies_list[$name] = $dependencies;
|
||||
if (count($dependencies) > 0) {
|
||||
// Set groups with dependencies aside for reordering
|
||||
$dependent_groups[$name] = $group;
|
||||
@@ -48,29 +71,11 @@ class MigrateGroup {
|
||||
$groups[$name] = $group;
|
||||
}
|
||||
}
|
||||
$iterations = 0;
|
||||
while (count($dependent_groups) > 0) {
|
||||
if ($iterations++ > 20) {
|
||||
$group_names = implode(',', array_keys($dependent_groups));
|
||||
throw new MigrateException(t('Failure to sort migration list - most likely due ' .
|
||||
'to circular dependencies involving groups !group_names',
|
||||
array('!group_names' => $group_names)));
|
||||
}
|
||||
foreach ($dependent_groups as $name => $group) {
|
||||
$ready = TRUE;
|
||||
// Scan all the dependencies for this group and make sure they're all
|
||||
// in the final list
|
||||
foreach ($group->getDependencies() as $dependency) {
|
||||
if (!isset($groups[$dependency])) {
|
||||
$ready = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($ready) {
|
||||
// Yes they are! Move this group to the final list
|
||||
$groups[$name] = $group;
|
||||
unset($dependent_groups[$name]);
|
||||
}
|
||||
|
||||
$ordered_groups = migrate_order_dependencies($dependencies_list);
|
||||
foreach ($ordered_groups as $name) {
|
||||
if (!isset($groups[$name])) {
|
||||
$groups[$name] = $dependent_groups[$name];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +91,10 @@ class MigrateGroup {
|
||||
* @param array $dependencies
|
||||
* List of dependent groups.
|
||||
*/
|
||||
public function __construct($name, $dependencies = array()) {
|
||||
public function __construct($name, $dependencies = array(), $title = '', $arguments = array()) {
|
||||
$this->name = $name;
|
||||
$this->title = $title;
|
||||
$this->arguments = $arguments;
|
||||
$this->dependencies = $dependencies;
|
||||
}
|
||||
|
||||
@@ -102,8 +109,94 @@ class MigrateGroup {
|
||||
*/
|
||||
static public function getInstance($name, $dependencies = array()) {
|
||||
if (empty(self::$groupList[$name])) {
|
||||
self::$groupList[$name] = new MigrateGroup($name, $dependencies);
|
||||
$row = db_select('migrate_group', 'mg')
|
||||
->fields('mg')
|
||||
->condition('name', $name)
|
||||
->execute()
|
||||
->fetchObject();
|
||||
if ($row) {
|
||||
$arguments = unserialize($row->arguments);
|
||||
$arguments = MigrationBase::decryptArguments($arguments);
|
||||
if (empty($dependencies) && isset($arguments['dependencies'])) {
|
||||
$dependencies = $arguments['dependencies'];
|
||||
}
|
||||
self::$groupList[$name] = new MigrateGroup($name, $dependencies,
|
||||
$row->title, $arguments);
|
||||
}
|
||||
else {
|
||||
self::register($name);
|
||||
self::$groupList[$name] = new MigrateGroup($name, $dependencies);
|
||||
}
|
||||
}
|
||||
return self::$groupList[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new migration group in the migrate_group table.
|
||||
*
|
||||
* @param string $name
|
||||
* The machine name (unique identifier) for the group.
|
||||
*
|
||||
* @param string $title
|
||||
* A user-visible title for the group. Defaults to the machine name.
|
||||
*
|
||||
* @param array $arguments
|
||||
* An array of group arguments - generally data that applies to all migrations
|
||||
* in the group.
|
||||
*/
|
||||
static public function register($name, $title = NULL, array $arguments = array()) {
|
||||
if (!$title) {
|
||||
$title = $name;
|
||||
}
|
||||
|
||||
$arguments = MigrationBase::encryptArguments($arguments);
|
||||
|
||||
// Register the migration if it's not already there; if it is,
|
||||
// update the class and arguments in case they've changed.
|
||||
db_merge('migrate_group')
|
||||
->key(array('name' => $name))
|
||||
->fields(array(
|
||||
'title' => $title,
|
||||
'arguments' => serialize($arguments)
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deregister a migration group - remove it from the database, and also
|
||||
* remove migrations attached to it.
|
||||
*
|
||||
* @param string $name
|
||||
* (Machine) name of the group to deregister.
|
||||
*/
|
||||
static public function deregister($name) {
|
||||
$result = db_select('migrate_status', 'ms')
|
||||
->fields('ms', array('machine_name'))
|
||||
->condition('group_name', $name)
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
Migration::deregisterMigration($row->machine_name);
|
||||
}
|
||||
|
||||
db_delete('migrate_group')
|
||||
->condition('name', $name)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any groups which no longer contain any migrations.
|
||||
*/
|
||||
static public function deleteOrphans() {
|
||||
$query = db_select('migrate_group', 'mg');
|
||||
$query->addField('mg', 'name');
|
||||
$query->leftJoin('migrate_status', 'ms', 'mg.name=ms.group_name');
|
||||
$query->isNull('ms.machine_name');
|
||||
$result = $query->execute();
|
||||
foreach ($result as $row) {
|
||||
db_delete('migrate_group')
|
||||
->condition('name', $row->name)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -43,12 +43,27 @@ abstract class MigrateMap implements Iterator {
|
||||
*/
|
||||
protected $sourceKeyMap, $destinationKeyMap;
|
||||
|
||||
/**
|
||||
* Get the source key map.
|
||||
*/
|
||||
public function getSourceKeyMap() {
|
||||
return $this->sourceKeyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean determining whether to track last_imported times in map tables
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $trackLastImported = FALSE;
|
||||
public function getTrackLastImported() {
|
||||
return $this->trackLastImported;
|
||||
}
|
||||
public function setTrackLastImported($trackLastImported) {
|
||||
if (is_bool($trackLastImported)) {
|
||||
$this->trackLastImported = $trackLastImported;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a mapping from the key values in the source row to the destination
|
||||
@@ -58,9 +73,11 @@ abstract class MigrateMap implements Iterator {
|
||||
* @param $dest_ids
|
||||
* @param $status
|
||||
* @param $rollback_action
|
||||
* @param $hash
|
||||
*/
|
||||
abstract public function saveIDMapping(stdClass $source_row, array $dest_ids,
|
||||
$status = MigrateMap::STATUS_IMPORTED, $rollback_action = MigrateMap::ROLLBACK_DELETE);
|
||||
$status = MigrateMap::STATUS_IMPORTED,
|
||||
$rollback_action = MigrateMap::ROLLBACK_DELETE, $hash = NULL);
|
||||
|
||||
/**
|
||||
* Record a message related to a source record
|
||||
|
@@ -22,6 +22,9 @@ abstract class Migration extends MigrationBase {
|
||||
public function getSource() {
|
||||
return $this->source;
|
||||
}
|
||||
public function setSource(MigrateSource $source) {
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destination object for the migration, derived from MigrateDestination.
|
||||
@@ -32,6 +35,9 @@ abstract class Migration extends MigrationBase {
|
||||
public function getDestination() {
|
||||
return $this->destination;
|
||||
}
|
||||
public function setDestination(MigrateDestination $destination) {
|
||||
$this->destination = $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map object tracking relationships between source and destination data
|
||||
@@ -42,6 +48,9 @@ abstract class Migration extends MigrationBase {
|
||||
public function getMap() {
|
||||
return $this->map;
|
||||
}
|
||||
public function setMap(MigrateMap $map) {
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the primary system of record for this migration is the
|
||||
@@ -59,6 +68,9 @@ abstract class Migration extends MigrationBase {
|
||||
public function getSystemOfRecord() {
|
||||
return $this->systemOfRecord;
|
||||
}
|
||||
public function setSystemOfRecord($system_of_record) {
|
||||
$this->systemOfRecord = $system_of_record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify value of needs_update for current map row. Usually set by
|
||||
@@ -75,6 +87,12 @@ abstract class Migration extends MigrationBase {
|
||||
* @var int
|
||||
*/
|
||||
protected $defaultRollbackAction = MigrateMap::ROLLBACK_DELETE;
|
||||
public function getDefaultRollbackAction() {
|
||||
return $this->defaultRollbackAction;
|
||||
}
|
||||
public function setDefaultRollbackAction($rollback_action) {
|
||||
$this->defaultRollbackAction = $rollback_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rollback action to be saved for the current row.
|
||||
@@ -84,13 +102,62 @@ abstract class Migration extends MigrationBase {
|
||||
public $rollbackAction;
|
||||
|
||||
/**
|
||||
* Simple mappings between destination fields (keys) and source fields (values).
|
||||
* Field mappings defined in code.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldMappings = array();
|
||||
protected $storedFieldMappings = array();
|
||||
protected $storedFieldMappingsRetrieved = FALSE;
|
||||
public function getStoredFieldMappings() {
|
||||
if (!$this->storedFieldMappingsRetrieved) {
|
||||
$this->loadFieldMappings();
|
||||
$this->storedFieldMappingsRetrieved = TRUE;
|
||||
}
|
||||
return $this->storedFieldMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field mappings retrieved from storage.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $codedFieldMappings = array();
|
||||
public function getCodedFieldMappings() {
|
||||
return $this->codedFieldMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* All field mappings, with those retrieved from the database overriding those
|
||||
* defined in code.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allFieldMappings = array();
|
||||
public function getFieldMappings() {
|
||||
return $this->fieldMappings;
|
||||
if (empty($allFieldMappings)) {
|
||||
$this->allFieldMappings = array_merge($this->getCodedFieldMappings(),
|
||||
$this->getStoredFieldMappings());
|
||||
// If there are multiple mappings of a given source field to no
|
||||
// destination field, keep only the last (so the UI can override a source
|
||||
// field DNM that was defined in code).
|
||||
$no_destination = array();
|
||||
foreach ($this->allFieldMappings as $destination_field => $mapping) {
|
||||
// If the source field is not mapped to a destination field, the
|
||||
// array index is integer.
|
||||
if (is_int($destination_field)) {
|
||||
$source_field = $mapping->getSourceField();
|
||||
if (isset($no_destination[$source_field])) {
|
||||
unset($this->allFieldMappings[$no_destination[$source_field]]);
|
||||
unset($no_destination[$source_field]);
|
||||
}
|
||||
$no_destination[$source_field] = $destination_field;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure primary fields come before their subfields
|
||||
ksort($this->allFieldMappings);
|
||||
}
|
||||
return $this->allFieldMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +186,9 @@ abstract class Migration extends MigrationBase {
|
||||
public function getHighwaterField() {
|
||||
return $this->highwaterField;
|
||||
}
|
||||
public function setHighwaterField(array $highwater_field) {
|
||||
$this->highwaterField = $highwater_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* The object currently being constructed
|
||||
@@ -143,8 +213,29 @@ abstract class Migration extends MigrationBase {
|
||||
/**
|
||||
* General initialization of a Migration object.
|
||||
*/
|
||||
public function __construct($group = NULL) {
|
||||
parent::__construct($group);
|
||||
public function __construct($arguments = array()) {
|
||||
parent::__construct($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new migration process in the migrate_status table. This will
|
||||
* generally be used in two contexts - by the class detection code for
|
||||
* static (one instance per class) migrations, and by the module implementing
|
||||
* dynamic (parameterized class) migrations.
|
||||
*
|
||||
* @param string $class_name
|
||||
* @param string $machine_name
|
||||
* @param array $arguments
|
||||
*/
|
||||
static public function registerMigration($class_name, $machine_name = NULL,
|
||||
array $arguments = array()) {
|
||||
// Record any field mappings provided via arguments.
|
||||
if (isset($arguments['field_mappings'])) {
|
||||
self::saveFieldMappings($machine_name, $arguments['field_mappings']);
|
||||
unset($arguments['field_mappings']);
|
||||
}
|
||||
|
||||
parent::registerMigration($class_name, $machine_name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,9 +252,16 @@ abstract class Migration extends MigrationBase {
|
||||
try {
|
||||
// Remove map and message tables
|
||||
$migration = self::getInstance($machine_name);
|
||||
$migration->map->destroy();
|
||||
if ($migration && method_exists($migration, 'getMap')) {
|
||||
$migration->getMap()->destroy();
|
||||
}
|
||||
|
||||
// TODO: Clear log entries? Or keep for historical purposes?
|
||||
// @todo: Clear log entries? Or keep for historical purposes?
|
||||
|
||||
// Remove stored field mappings for this migration
|
||||
$rows_deleted = db_delete('migrate_field_mapping')
|
||||
->condition('machine_name', $machine_name)
|
||||
->execute();
|
||||
|
||||
// Call the parent deregistration (which clears migrate_status) last, the
|
||||
// above will reference it.
|
||||
@@ -174,6 +272,51 @@ abstract class Migration extends MigrationBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an array of field mappings to the database.
|
||||
*
|
||||
* @param $machine_name
|
||||
* @param array $field_mappings
|
||||
*/
|
||||
static public function saveFieldMappings($machine_name, array $field_mappings) {
|
||||
// Clear existing field mappings
|
||||
db_delete('migrate_field_mapping')
|
||||
->condition('machine_name', $machine_name)
|
||||
->execute();
|
||||
foreach ($field_mappings as $field_mapping) {
|
||||
$destination_field = $field_mapping->getDestinationField();
|
||||
$source_field = $field_mapping->getSourceField();
|
||||
db_insert('migrate_field_mapping')
|
||||
->fields(array(
|
||||
'machine_name' => $machine_name,
|
||||
'destination_field' => is_null($destination_field) ? '' : $destination_field,
|
||||
'source_field' => is_null($source_field) ? '' : $source_field,
|
||||
'options' => serialize($field_mapping)
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load any stored field mappings from the database.
|
||||
*/
|
||||
public function loadFieldMappings() {
|
||||
$result = db_select('migrate_field_mapping', 'mfm')
|
||||
->fields('mfm', array('destination_field', 'source_field', 'options'))
|
||||
->condition('machine_name', $this->machineName)
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
$field_mapping = unserialize($row->options);
|
||||
$field_mapping->setMappingSource(MigrateFieldMapping::MAPPING_SOURCE_DB);
|
||||
if (empty($row->destination_field)) {
|
||||
$this->storedFieldMappings[] = $field_mapping;
|
||||
}
|
||||
else {
|
||||
$this->storedFieldMappings[$row->destination_field] = $field_mapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Processing
|
||||
|
||||
@@ -193,25 +336,25 @@ abstract class Migration extends MigrationBase {
|
||||
$warn_on_override = TRUE) {
|
||||
// Warn of duplicate mappings
|
||||
if ($warn_on_override && !is_null($destination_field) &&
|
||||
isset($this->fieldMappings[$destination_field])) {
|
||||
isset($this->codedFieldMappings[$destination_field])) {
|
||||
self::displayMessage(
|
||||
t('!name addFieldMapping: !dest was previously mapped from !source, overridden',
|
||||
array('!name' => $this->machineName, '!dest' => $destination_field,
|
||||
'!source' => $this->fieldMappings[$destination_field]->getSourceField())),
|
||||
'!source' => $this->codedFieldMappings[$destination_field]->getSourceField())),
|
||||
'warning');
|
||||
}
|
||||
$mapping = new MigrateFieldMapping($destination_field, $source_field);
|
||||
if (is_null($destination_field)) {
|
||||
$this->fieldMappings[] = $mapping;
|
||||
$this->codedFieldMappings[] = $mapping;
|
||||
}
|
||||
else {
|
||||
$this->fieldMappings[$destination_field] = $mapping;
|
||||
$this->codedFieldMappings[$destination_field] = $mapping;
|
||||
}
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any existing mappings for a given destination or source field.
|
||||
* Remove any existing coded mappings for a given destination or source field.
|
||||
*
|
||||
* @param string $destination_field
|
||||
* Name of the destination field.
|
||||
@@ -220,12 +363,12 @@ abstract class Migration extends MigrationBase {
|
||||
*/
|
||||
public function removeFieldMapping($destination_field, $source_field = NULL) {
|
||||
if (isset($destination_field)) {
|
||||
unset($this->fieldMappings[$destination_field]);
|
||||
unset($this->codedFieldMappings[$destination_field]);
|
||||
}
|
||||
if (isset($source_field)) {
|
||||
foreach ($this->fieldMappings as $key => $mapping) {
|
||||
foreach ($this->codedFieldMappings as $key => $mapping) {
|
||||
if ($mapping->getSourceField() == $source_field) {
|
||||
unset($this->fieldMappings[$key]);
|
||||
unset($this->codedFieldMappings[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,12 +397,12 @@ abstract class Migration extends MigrationBase {
|
||||
* @param string $issue_group
|
||||
* Issue group name to apply to the generated mappings (defaults to 'DNM').
|
||||
*/
|
||||
public function addUnmigratedDestinations(array $fields, $issue_group = NULL) {
|
||||
public function addUnmigratedDestinations(array $fields, $issue_group = NULL, $warn_on_override = TRUE) {
|
||||
if (!$issue_group) {
|
||||
$issue_group = t('DNM');
|
||||
}
|
||||
foreach ($fields as $field) {
|
||||
$this->addFieldMapping($field)
|
||||
$this->addFieldMapping($field, NULL, $warn_on_override)
|
||||
->issueGroup($issue_group);
|
||||
}
|
||||
}
|
||||
@@ -274,12 +417,12 @@ abstract class Migration extends MigrationBase {
|
||||
* @param string $issue_group
|
||||
* Issue group name to apply to the generated mappings (defaults to 'DNM').
|
||||
*/
|
||||
public function addUnmigratedSources(array $fields, $issue_group = NULL) {
|
||||
public function addUnmigratedSources(array $fields, $issue_group = NULL, $warn_on_override = TRUE) {
|
||||
if (!$issue_group) {
|
||||
$issue_group = t('DNM');
|
||||
}
|
||||
foreach ($fields as $field) {
|
||||
$this->addFieldMapping(NULL, $field)
|
||||
$this->addFieldMapping(NULL, $field, $warn_on_override)
|
||||
->issueGroup($issue_group);
|
||||
}
|
||||
}
|
||||
@@ -289,7 +432,7 @@ abstract class Migration extends MigrationBase {
|
||||
* source rows have been processed).
|
||||
*/
|
||||
public function isComplete() {
|
||||
$total = $this->source->count(TRUE);
|
||||
$total = $this->sourceCount(TRUE);
|
||||
// If the source is uncountable, we have no way of knowing if it's
|
||||
// complete, so stipulate that it is.
|
||||
if ($total < 0) {
|
||||
@@ -551,8 +694,8 @@ abstract class Migration extends MigrationBase {
|
||||
}
|
||||
catch (Exception $e) {
|
||||
self::displayMessage(
|
||||
t('Migration failed with source plugin exception: !e',
|
||||
array('!e' => $e->getMessage())));
|
||||
t('Migration failed with source plugin exception: %e, in %file:%line',
|
||||
array('%e' => $e->getMessage(), '%file' => $e->getFile(), '%line' => $e->getLine())));
|
||||
return MigrationBase::RESULT_FAILED;
|
||||
}
|
||||
while ($this->source->valid()) {
|
||||
@@ -570,28 +713,33 @@ abstract class Migration extends MigrationBase {
|
||||
$ids = $this->destination->import($this->destinationValues, $this->sourceValues);
|
||||
migrate_instrument_stop('destination import');
|
||||
if ($ids) {
|
||||
$this->map->saveIDMapping($this->sourceValues, $ids, $this->needsUpdate,
|
||||
$this->rollbackAction);
|
||||
$this->map->saveIDMapping($this->sourceValues, $ids,
|
||||
$this->needsUpdate, $this->rollbackAction,
|
||||
$data_row->migrate_map_hash);
|
||||
$this->successes_since_feedback++;
|
||||
$this->total_successes++;
|
||||
}
|
||||
else {
|
||||
$this->map->saveIDMapping($this->sourceValues, array(),
|
||||
MigrateMap::STATUS_FAILED, $this->rollbackAction);
|
||||
$message = t('New object was not saved, no error provided');
|
||||
$this->saveMessage($message);
|
||||
self::displayMessage($message);
|
||||
MigrateMap::STATUS_FAILED, $this->rollbackAction,
|
||||
$data_row->migrate_map_hash);
|
||||
if ($this->map->messageCount() == 0) {
|
||||
$message = t('New object was not saved, no error provided');
|
||||
$this->saveMessage($message);
|
||||
self::displayMessage($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->map->saveIDMapping($this->sourceValues, array(),
|
||||
MigrateMap::STATUS_FAILED, $this->rollbackAction);
|
||||
$e->getStatus(), $this->rollbackAction, $data_row->migrate_map_hash);
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
self::displayMessage($e->getMessage());
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->map->saveIDMapping($this->sourceValues, array(),
|
||||
MigrateMap::STATUS_FAILED, $this->rollbackAction);
|
||||
MigrateMap::STATUS_FAILED, $this->rollbackAction,
|
||||
$data_row->migrate_map_hash);
|
||||
$this->handleException($e);
|
||||
}
|
||||
$this->total_processed++;
|
||||
@@ -624,8 +772,8 @@ abstract class Migration extends MigrationBase {
|
||||
}
|
||||
catch (Exception $e) {
|
||||
self::displayMessage(
|
||||
t('Migration failed with source plugin exception: !e',
|
||||
array('!e' => $e->getMessage())));
|
||||
t('Migration failed with source plugin exception: %e, in %file:%line',
|
||||
array('%e' => $e->getMessage(), '%file' => $e->getFile(), '%line' => $e->getLine())));
|
||||
return MigrationBase::RESULT_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -679,7 +827,7 @@ abstract class Migration extends MigrationBase {
|
||||
$row = $this->source->current();
|
||||
// Cheat for XML migrations, which don't pick up the source values
|
||||
// until applyMappings() applies the xpath()
|
||||
if (is_a($this, 'XMLMigration')) {
|
||||
if (is_a($this, 'XMLMigration') && isset($row->xml)) {
|
||||
$this->sourceValues = $row;
|
||||
$this->applyMappings();
|
||||
$row = $this->sourceValues;
|
||||
@@ -1038,7 +1186,7 @@ abstract class Migration extends MigrationBase {
|
||||
*/
|
||||
protected function applyMappings() {
|
||||
$this->destinationValues = new stdClass;
|
||||
foreach ($this->fieldMappings as $mapping) {
|
||||
foreach ($this->getFieldMappings() as $mapping) {
|
||||
$destination = $mapping->getDestinationField();
|
||||
// Skip mappings with no destination (source fields marked DNM)
|
||||
if ($destination) {
|
||||
@@ -1055,7 +1203,7 @@ abstract class Migration extends MigrationBase {
|
||||
|
||||
// If there's a source mapping, and a source value in the data row, copy
|
||||
// to the destination
|
||||
if ($source && property_exists($this->sourceValues, $source)) {
|
||||
if ($source && isset($this->sourceValues->{$source})) {
|
||||
$destination_values = $this->sourceValues->$source;
|
||||
}
|
||||
// Otherwise, apply the default value (if any)
|
||||
@@ -1141,7 +1289,8 @@ abstract class Migration extends MigrationBase {
|
||||
}
|
||||
// We've seen a subfield, so add as an array value.
|
||||
else {
|
||||
$this->destinationValues->{$destination_field}[] = $destination_values;
|
||||
$this->destinationValues->{$destination_field} = array_merge(
|
||||
(array)$destination_values, $this->destinationValues->{$destination_field});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1202,15 +1351,17 @@ abstract class Migration extends MigrationBase {
|
||||
$results = array();
|
||||
// Each $source_key will be an array of key values
|
||||
foreach ($source_keys as $source_key) {
|
||||
// If any source keys are empty, skip this set
|
||||
// If any source keys are NULL, skip this set
|
||||
$continue = FALSE;
|
||||
foreach ($source_key as $value) {
|
||||
if (empty($value) && $value !== 0 && $value !== '0') {
|
||||
if (!isset($value)) {
|
||||
$continue = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($continue || empty($source_key)) {
|
||||
// Occasionally $source_key comes through with an empty string.
|
||||
$sanity_check = array_filter($source_key);
|
||||
if ($continue || empty($source_key) || empty($sanity_check)) {
|
||||
continue;
|
||||
}
|
||||
// Loop through each source migration, checking for an existing dest ID.
|
||||
@@ -1261,7 +1412,7 @@ abstract class Migration extends MigrationBase {
|
||||
}
|
||||
else {
|
||||
$value = reset($results);
|
||||
return empty($value) ? NULL : $value;
|
||||
return empty($value) && $value !== 0 && $value !== '0' ? NULL : $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1372,7 +1523,7 @@ abstract class Migration extends MigrationBase {
|
||||
/**
|
||||
* Save any messages we've queued up to the message table.
|
||||
*/
|
||||
protected function saveQueuedMessages() {
|
||||
public function saveQueuedMessages() {
|
||||
foreach ($this->queuedMessages as $queued_message) {
|
||||
$this->saveMessage($queued_message['message'], $queued_message['level']);
|
||||
}
|
||||
@@ -1391,12 +1542,19 @@ abstract class Migration extends MigrationBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience class - deriving from this rather than directory from Migration
|
||||
* ensures that a class will not be registered as a migration itself - it is
|
||||
* the implementor's responsibility to register each instance of a dynamic
|
||||
* migration class.
|
||||
* @deprecated - This class is no longer necessary, inherit directly from
|
||||
* Migration instead.
|
||||
*/
|
||||
abstract class DynamicMigration extends Migration {
|
||||
static $deprecationWarning = FALSE;
|
||||
public function __construct($arguments) {
|
||||
parent::__construct($arguments);
|
||||
if (variable_get('migrate_deprecation_warnings', 1) &&
|
||||
!self::$deprecationWarning) {
|
||||
self::displayMessage(t('The DynamicMigration class is no longer necessary and is now deprecated - please derive your migration classes directly from Migration.'));
|
||||
self::$deprecationWarning = TRUE;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Overrides default of FALSE
|
||||
*/
|
||||
|
@@ -78,6 +78,20 @@ abstract class MigrateSource implements Iterator {
|
||||
*/
|
||||
protected $highwaterField;
|
||||
|
||||
/**
|
||||
* The highwater mark at the beginning of the import operation.
|
||||
*
|
||||
* @var
|
||||
*/
|
||||
protected $originalHighwater = '';
|
||||
|
||||
/**
|
||||
* Used in the case of multiple key sources that need to use idlist.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $multikeySeparator = ':';
|
||||
|
||||
/**
|
||||
* List of source IDs to process.
|
||||
*
|
||||
@@ -116,6 +130,14 @@ abstract class MigrateSource implements Iterator {
|
||||
*/
|
||||
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
|
||||
@@ -186,6 +208,9 @@ abstract class MigrateSource implements Iterator {
|
||||
if (!empty($options['cache_key'])) {
|
||||
$this->cacheKey = $options['cache_key'];
|
||||
}
|
||||
if (!empty($options['track_changes'])) {
|
||||
$this->trackChanges = $options['track_changes'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,6 +254,9 @@ abstract class MigrateSource implements Iterator {
|
||||
$this->numProcessed = 0;
|
||||
$this->numIgnored = 0;
|
||||
$this->highwaterField = $this->activeMigration->getHighwaterField();
|
||||
if (!empty($this->highwaterField)) {
|
||||
$this->originalHighwater = $this->activeMigration->getHighwater();
|
||||
}
|
||||
if ($this->activeMigration->getOption('idlist')) {
|
||||
$this->idList = explode(',', $this->activeMigration->getOption('idlist'));
|
||||
}
|
||||
@@ -248,9 +276,11 @@ abstract class MigrateSource implements Iterator {
|
||||
public function next() {
|
||||
$this->currentKey = NULL;
|
||||
$this->currentRow = NULL;
|
||||
|
||||
migrate_instrument_start(get_class($this) . ' getNextRow');
|
||||
while ($row = $this->getNextRow()) {
|
||||
migrate_instrument_stop(get_class($this) . ' getNextRow');
|
||||
|
||||
// Populate the source key for this row
|
||||
$this->currentKey = $this->activeMigration->prepareKey(
|
||||
$this->activeMap->getSourceKey(), $row);
|
||||
@@ -267,23 +297,22 @@ abstract class MigrateSource implements Iterator {
|
||||
}
|
||||
}
|
||||
|
||||
// First, determine if this row should be passed to prepareRow(), or skipped
|
||||
// entirely. The rules are:
|
||||
// First, determine if this row should be passed to prepareRow(), or
|
||||
// skipped entirely. The rules are:
|
||||
// 1. If there's an explicit idlist, that's all we care about (ignore
|
||||
// highwaters and map rows).
|
||||
$prepared = FALSE;
|
||||
if (!empty($this->idList)) {
|
||||
if (in_array(reset($this->currentKey), $this->idList)) {
|
||||
// In the list, fall through.
|
||||
}
|
||||
else {
|
||||
// Not in the list, skip it
|
||||
$this->currentRow = NULL;
|
||||
continue;
|
||||
if (!in_array(reset($this->currentKey), $this->idList)) {
|
||||
$compoundKey = implode($this->multikeySeparator, $this->currentKey);
|
||||
if (count($this->currentKey) > 1 && !in_array($compoundKey, $this->idList)) {
|
||||
// Could not find the key, skip.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. If the row is not in the map (we have never tried to import it before),
|
||||
// we always want to try it.
|
||||
// 2. If the row is not in the map (we have never tried to import it
|
||||
// before), we always want to try it.
|
||||
elseif (!isset($row->migrate_map_sourceid1)) {
|
||||
// Fall through
|
||||
}
|
||||
@@ -293,36 +322,52 @@ abstract class MigrateSource implements Iterator {
|
||||
}
|
||||
// 4. At this point, we have a row which has previously been imported and
|
||||
// not marked for update. If we're not using highwater marks, then we
|
||||
// will not take this row.
|
||||
// will not take this row. Except, if we're looking for changes in the
|
||||
// data, we need to go through prepareRow() before we can decide to
|
||||
// skip it.
|
||||
elseif (empty($this->highwaterField)) {
|
||||
// No highwater, skip
|
||||
$this->currentRow = NULL;
|
||||
continue;
|
||||
if ($this->trackChanges) {
|
||||
if ($this->prepareRow($row) !== FALSE) {
|
||||
if ($this->dataChanged($row)) {
|
||||
// This is a keeper
|
||||
$this->currentRow = $row;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// No change, skip it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// prepareRow() told us to skip it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No highwater and not tracking changes, skip.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 5. The initial highwater mark, before anything is migrated, is ''. We
|
||||
// want to make sure we don't mistakenly skip rows with a highwater
|
||||
// field value of 0, so explicitly handle '' here.
|
||||
elseif ($this->activeMigration->getHighwater() === '') {
|
||||
elseif ($this->originalHighwater === '') {
|
||||
// Fall through
|
||||
}
|
||||
// 6. So, we are using highwater marks. Take the row if its highwater field
|
||||
// value is greater than the saved mark, otherwise skip it.
|
||||
// 6. So, we are using highwater marks. Take the row if its highwater
|
||||
// field value is greater than the saved mark, otherwise skip it.
|
||||
else {
|
||||
// Call prepareRow() here, in case the highwaterField needs preparation
|
||||
if ($this->prepareRow($row) !== FALSE) {
|
||||
if ($row->{$this->highwaterField['name']} > $this->activeMigration->getHighwater()) {
|
||||
if ($row->{$this->highwaterField['name']} > $this->originalHighwater) {
|
||||
$this->currentRow = $row;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// Skip
|
||||
$this->currentRow = NULL;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->currentRow = NULL;
|
||||
}
|
||||
$prepared = TRUE;
|
||||
}
|
||||
|
||||
@@ -358,6 +403,10 @@ abstract class MigrateSource implements Iterator {
|
||||
migrate_instrument_stop(get_class($this->activeMigration) . ' prepareRow');
|
||||
// We're explicitly skipping this row - keep track in the map table
|
||||
if ($return === FALSE) {
|
||||
// Make sure we replace any previous messages for this item with any
|
||||
// new ones.
|
||||
$this->activeMigration->getMap()->delete($this->currentKey, TRUE);
|
||||
$this->activeMigration->saveQueuedMessages();
|
||||
$this->activeMigration->getMap()->saveIDMapping($row, array(),
|
||||
MigrateMap::STATUS_IGNORED, $this->activeMigration->rollbackAction);
|
||||
$this->numIgnored++;
|
||||
@@ -366,8 +415,62 @@ abstract class MigrateSource implements Iterator {
|
||||
}
|
||||
else {
|
||||
$return = TRUE;
|
||||
// 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).
|
||||
if ($this->trackChanges) {
|
||||
$unhashed_row = clone ($row);
|
||||
// Remove all map data, otherwise we'll have a false positive on the
|
||||
// second import (attempt) on a row.
|
||||
foreach ($unhashed_row as $field => $data) {
|
||||
if (strpos($field, 'migrate_map_') === 0) {
|
||||
unset($unhashed_row->$field);
|
||||
}
|
||||
}
|
||||
$row->migrate_map_original_hash = isset($row->migrate_map_hash) ?
|
||||
$row->migrate_map_hash : '';
|
||||
$row->migrate_map_hash = $this->hash($unhashed_row);
|
||||
}
|
||||
else {
|
||||
$row->migrate_map_hash = '';
|
||||
}
|
||||
}
|
||||
|
||||
$this->numProcessed++;
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this row has changed, and therefore whether it should
|
||||
* be processed.
|
||||
*
|
||||
* @param $row
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function dataChanged($row) {
|
||||
if ($row->migrate_map_original_hash != $row->migrate_map_hash) {
|
||||
$return = TRUE;
|
||||
}
|
||||
else {
|
||||
$return = FALSE;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash of the source row.
|
||||
*
|
||||
* @param $row
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function hash($row) {
|
||||
migrate_instrument_start('MigrateSource::hash');
|
||||
$hash = md5(serialize($row));
|
||||
migrate_instrument_stop('MigrateSource::hash');
|
||||
return $hash;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user