non security modules update

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-20 16:32:07 +02:00
parent 6a8d30db08
commit 37fbabab56
466 changed files with 32690 additions and 9652 deletions

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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;
}
}