first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -0,0 +1,295 @@
<?php
/**
* @file
* Support for comment destinations.
*/
// TODO:
// Make sure this works with updates, explicit destination keys
/**
* Destination class implementing migration into comments.
*/
class MigrateDestinationComment extends MigrateDestinationEntity {
static public function getKeySchema() {
return array(
'cid' => array(
'type' => 'int',
'unsigned' => TRUE,
'description' => 'ID of destination entity',
),
);
}
/**
* Save the original setting of comment_maintain_node_statistics
* @var boolean
*/
protected $maintainNodeStatistics;
/**
* Return an options array for comment destinations.
*
* @param string $language
* Default language for comments created via this destination class.
* @param string $text_format
* Default text format for comments created via this destination class.
*/
static public function options($language, $text_format) {
return compact('language', 'text_format');
}
/**
* Basic initialization
*
* @param string $bundle
* A.k.a. the content type (page, article, etc.) of the ... comment?.
* @param array $options
* Options applied to comments.
*/
public function __construct($bundle, array $options = array()) {
parent::__construct('comment', $bundle, $options);
}
/**
* Returns a list of fields available to be mapped for comments attached to
* a particular bundle (node type)
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array();
// First the core (comment table) properties
$fields['cid'] = t('Comment: <a href="@doc">Existing comment ID</a>',
array('@doc' => 'http://drupal.org/node/1349714#cid'));
$fields['nid'] = t('Comment: <a href="@doc">Node (by Drupal ID)</a>',
array('@doc' => 'http://drupal.org/node/1349714#nid'));
$fields['uid'] = t('Comment: <a href="@doc">User (by Drupal ID)</a>',
array('@doc' => 'http://drupal.org/node/1349714#uid'));
$fields['pid'] = t('Comment: <a href="@doc">Parent (by Drupal ID)</a>',
array('@doc' => 'http://drupal.org/node/1349714#pid'));
$fields['subject'] = t('Comment: <a href="@doc">Subject</a>',
array('@doc' => 'http://drupal.org/node/1349714#subject'));
$fields['created'] = t('Comment: <a href="@doc">Created timestamp</a>',
array('@doc' => 'http://drupal.org/node/1349714#created'));
$fields['changed'] = t('Comment: <a href="@doc">Modified timestamp</a>',
array('@doc' => 'http://drupal.org/node/1349714#changed'));
$fields['status'] = t('Comment: <a href="@doc">Status</a>',
array('@doc' => 'http://drupal.org/node/1349714#status'));
$fields['hostname'] = t('Comment: <a href="@doc">Hostname/IP address</a>',
array('@doc' => 'http://drupal.org/node/1349714#hostname'));
$fields['name'] = t('Comment: <a href="@doc">User name (not username)</a>',
array('@doc' => 'http://drupal.org/node/1349714#name'));
$fields['mail'] = t('Comment: <a href="@doc">Email address</a>',
array('@doc' => 'http://drupal.org/node/1349714#mail'));
$fields['homepage'] = t('Comment: <a href="@doc">Homepage</a>',
array('@doc' => 'http://drupal.org/node/1349714#homepage'));
$fields['language'] = t('Comment: <a href="@doc">Language</a>',
array('@doc' => 'http://drupal.org/node/1349714#language'));
$fields['thread'] = t('Comment: <a href="@doc">Thread</a>',
array('@doc' => 'http://drupal.org/node/1349714#thread'));
// Then add in anything provided by handlers
$fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle, $migration);
$fields += migrate_handler_invoke_all('Comment', 'fields', $this->entityType, $this->bundle, $migration);
return $fields;
}
/**
* Delete a batch of comments at once.
*
* @param $cids
* Array of comment IDs to be deleted.
*/
public function bulkRollback(array $cids) {
migrate_instrument_start('comment_delete_multiple');
$this->prepareRollback($cids);
$result = comment_delete_multiple($cids);
$this->completeRollback($cids);
migrate_instrument_stop('comment_delete_multiple');
return $result;
}
/**
* Import a single comment.
*
* @param $comment
* Comment object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields (cid only in this case) of the comment that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $comment, stdClass $row) {
$migration = Migration::currentMigration();
// Updating previously-migrated content?
if (isset($row->migrate_map_destid1)) {
if (isset($comment->cid)) {
if ($comment->cid != $row->migrate_map_destid1) {
throw new MigrateException(t("Incoming cid !cid and map destination nid !destid1 don't match",
array('!cid' => $comment->cid, '!destid1' => $row->migrate_map_destid1)));
}
}
else {
$comment->cid = $row->migrate_map_destid1;
}
}
// Fix up timestamps
if (isset($comment->created)) {
$comment->created = MigrationBase::timestamp($comment->created);
}
if (isset($comment->changed)) {
$comment->changed = MigrationBase::timestamp($comment->changed);
}
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
if (!isset($comment->cid)) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination cid provided'));
}
$rawcomment = $comment;
$old_comment = comment_load($comment->cid);
if (empty($old_comment)) {
throw new MigrateException(t('System-of-record is DESTINATION, but commend !cid does not exist',
array('!cid' => $comment->cid)));
}
if (!isset($comment->nid)) {
$comment->nid = $old_comment->nid;
}
if (!isset($comment->created)) {
$comment->created = $old_comment->created;
}
if (!isset($comment->changed)) {
$comment->changed = $old_comment->changed;
}
$this->prepare($comment, $row);
foreach ($rawcomment as $field => $value) {
$old_comment->$field = $comment->$field;
}
$comment = $old_comment;
}
else {
// Set some default properties.
$defaults = array(
'language' => $this->language,
'node_type' => $this->bundle,
'subject' => '',
'status' => COMMENT_PUBLISHED,
'uid' => 0,
'cid' => 0,
'pid' => 0,
);
foreach ($defaults as $field => $value) {
if (!isset($comment->$field)) {
$comment->$field = $value;
}
}
$this->prepare($comment, $row);
// Make sure we have a nid
if (!isset($comment->nid) || !$comment->nid) {
throw new MigrateException(t('No node ID provided for comment'));
}
// comment_save() hardcodes hostname, so if we're trying to set it we
// need to save it and apply it after
if (isset($comment->hostname)) {
$hostname = $comment->hostname;
}
}
if (isset($comment->cid) && $comment->cid) {
$updating = TRUE;
}
else {
$updating = FALSE;
}
migrate_instrument_start('comment_save');
comment_save($comment);
migrate_instrument_stop('comment_save');
if (isset($hostname) && isset($comment->cid) && $comment->cid > 0) {
db_update('comment')
->fields(array('hostname' => $hostname))
->condition('cid', $comment->cid)
->execute();
}
$this->complete($comment, $row);
if (isset($comment->cid) && $comment->cid > 0) {
$return = array($comment->cid);
if ($updating) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
}
else {
$return = FALSE;
}
return $return;
}
public function preImport() {
// If maintaining node statistics is enabled, temporarily disable it
$this->maintainNodeStatistics =
variable_get('comment_maintain_node_statistics', TRUE);
if ($this->maintainNodeStatistics) {
$GLOBALS['conf']['comment_maintain_node_statistics'] = FALSE;
}
}
public function postImport() {
// If originally enabled, re-enable and rebuild the stats
if ($this->maintainNodeStatistics) {
$GLOBALS['conf']['comment_maintain_node_statistics'] = TRUE;
// Copied from devel_rebuild_node_comment_statistics
// Empty table
db_truncate('node_comment_statistics')->execute();
// TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case when
// two comments on the same node share same timestamp.
$sql = "
INSERT IGNORE INTO {node_comment_statistics} (nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) (
SELECT c.nid, c.cid, c.created, c.name, c.uid, c2.comment_count FROM {comment} c
JOIN (
SELECT c.nid, MAX(c.created) AS created, COUNT(*) AS comment_count FROM {comment} c WHERE status=:published GROUP BY c.nid
) AS c2 ON c.nid = c2.nid AND c.created=c2.created
)";
db_query($sql, array(':published' => COMMENT_PUBLISHED));
// Insert records into the node_comment_statistics for nodes that are missing.
$query = db_select('node', 'n');
$query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid');
$query->addField('n', 'changed', 'last_comment_timestamp');
$query->addField('n', 'uid', 'last_comment_uid');
$query->addField('n', 'nid');
$query->addExpression('0', 'comment_count');
$query->addExpression('NULL', 'last_comment_name');
$query->isNull('ncs.comment_count');
db_insert('node_comment_statistics')
->from($query)
->execute();
}
}
}
class MigrateCommentNodeHandler extends MigrateDestinationHandler {
public function __construct() {
$this->registerTypes(array('node'));
}
public function fields($entity_type, $bundle) {
$fields = array();
$fields['comment'] = t('Whether comments may be posted to the node');
return $fields;
}
}

View File

@@ -0,0 +1,174 @@
<?php
/**
* @file
* Defines base for migration destinations implemented as Drupal entities.
*/
/**
* Abstract base class for entity-based destination handling. Holds common
* Field API-related functions.
*/
abstract class MigrateDestinationEntity extends MigrateDestination {
/**
* The entity type (node, user, taxonomy_term, etc.) of the destination.
*
* @var string
*/
protected $entityType;
public function getEntityType() {
return $this->entityType;
}
/**
* The bundle (node type, vocabulary, etc.) of the destination.
*
* @var string
*/
protected $bundle;
public function getBundle() {
return $this->bundle;
}
/**
* Default language for text fields in this destination.
*
* @var string
*/
protected $language;
public function getLanguage() {
return $this->language;
}
/**
* Default input format for text fields in this destination.
*
* @var int
*/
protected $textFormat;
public function getTextFormat() {
return $this->textFormat;
}
/**
* Simply save the key schema.
*
* @param array $key_schema
*/
public function __construct($entity_type, $bundle, array $options = array()) {
parent::__construct();
$this->entityType = $entity_type;
$this->bundle = $bundle;
$this->language = isset($options['language']) ? $options['language'] : LANGUAGE_NONE;
$this->textFormat = isset($options['text_format']) ? $options['text_format'] : filter_fallback_format();
}
public function __toString() {
// TODO: Link to configuration page
if ($this->entityType == $this->bundle) {
$output = t('%type', array('%type' => $this->entityType));
}
else {
$output = t('%type (%bundle)',
array('%type' => $this->entityType, '%bundle' => $this->bundle));
}
// TODO: Non-default language, input format
return $output;
}
/**
* Give handlers a shot at cleaning up before an entity has been rolled back.
*
* @param $entity_id
* ID of the entity about to be deleted..
*/
public function prepareRollback($entity_id) {
$migration = Migration::currentMigration();
// Call any general entity handlers (in particular, the builtin field handler)
migrate_handler_invoke_all('Entity', 'prepareRollback', $entity_id);
// Then call any entity-specific handlers
migrate_handler_invoke_all($this->entityType, 'prepareRollback', $entity_id);
// Then call any prepare handler for this specific Migration.
if (method_exists($migration, 'prepareRollback')) {
$migration->prepareRollback($entity_id);
}
}
/**
* Give handlers a shot at cleaning up after an entity has been rolled back.
*
* @param $entity_id
* ID of the entity which has been deleted.
*/
public function completeRollback($entity_id) {
$migration = Migration::currentMigration();
// Call any general entity handlers (in particular, the builtin field handler)
migrate_handler_invoke_all('Entity', 'completeRollback', $entity_id);
// Then call any entity-specific handlers
migrate_handler_invoke_all($this->entityType, 'completeRollback', $entity_id);
// Then call any complete handler for this specific Migration.
if (method_exists($migration, 'completeRollback')) {
$migration->completeRollback($entity_id);
}
}
/**
* Give handlers a shot at modifying the object before saving it.
*
* @param $entity
* Entity object to build. Prefilled with any fields mapped in the Migration.
* @param $source_row
* Raw source data object - passed through to prepare handlers.
*/
public function prepare($entity, stdClass $source_row) {
// Add source keys for debugging and identification of migrated data by hooks.
/* TODO: Restore
foreach ($migration->sourceKeyMap() as $field_name => $key_name) {
$keys[$key_name] = $source_row->$field_name;
}
*/
$migration = Migration::currentMigration();
$entity->migrate = array(
// 'source_keys' => $keys,
'machineName' => $migration->getMachineName(),
);
// Call any general entity handlers (in particular, the builtin field handler)
migrate_handler_invoke_all('Entity', 'prepare', $entity, $source_row);
// Then call any entity-specific handlers
migrate_handler_invoke_all($this->entityType, 'prepare', $entity, $source_row);
// Then call any prepare handler for this specific Migration.
if (method_exists($migration, 'prepare')) {
$migration->prepare($entity, $source_row);
}
}
/**
* Give handlers a shot at modifying the object (or taking additional action)
* after saving it.
*
* @param $object
* Entity object to build. This is the complete object after saving.
* @param $source_row
* Raw source data object - passed through to complete handlers.
*/
public function complete($entity, stdClass $source_row) {
// Call any general entity handlers (in particular, the builtin field handler)
migrate_handler_invoke_all('Entity', 'complete', $entity, $source_row);
// Then call any entity-specific handlers
migrate_handler_invoke_all($this->entityType, 'complete', $entity, $source_row);
// Then call any complete handler for this specific Migration.
$migration = Migration::currentMigration();
if (method_exists($migration, 'complete')) {
try {
$migration->complete($entity, $source_row);
}
catch (Exception $e) {
// If we catch any errors here, save the messages without letting
// the exception prevent the saving of the entity being recorded.
$migration->saveMessage($e->getMessage());
}
}
}
}

View File

@@ -0,0 +1,671 @@
<?php
/**
* @file
* Support for processing entity fields
*/
class MigrateFieldsEntityHandler extends MigrateDestinationHandler {
public function __construct() {
$this->registerTypes(array('entity'));
}
/**
* Implementation of MigrateDestinationHandler::fields().
*
* @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.
*/
public function fields($entity_type, $bundle, $migration = NULL) {
$fields = array();
$field_instance_info = field_info_instances($entity_type, $bundle);
foreach ($field_instance_info as $machine_name => $instance) {
$field_info = field_info_field($machine_name);
$type = $field_info['type'];
$fields[$machine_name] = t('Field:') . ' ' . $instance['label'] .
' (' . $field_info['type'] . ')';
// Look for subfields
$class_list = _migrate_class_list('MigrateFieldHandler');
$disabled = unserialize(variable_get('migrate_disabled_handlers', serialize(array())));
foreach ($class_list as $class_name => $handler) {
if (!in_array($class_name, $disabled) && $handler->handlesType($type)
&& method_exists($handler, 'fields')) {
migrate_instrument_start($class_name . '->fields');
$subfields = call_user_func(array($handler, 'fields'), $type,
$machine_name, $migration);
migrate_instrument_stop($class_name . '->fields');
foreach ($subfields as $subfield_name => $subfield_label) {
$fields[$machine_name . ':' . $subfield_name] = $subfield_label;
}
}
}
}
return $fields;
}
public function prepare($entity, stdClass $row) {
migrate_instrument_start('MigrateDestinationEntity->prepareFields');
// Look for Field API fields attached to this destination and handle appropriately
$migration = Migration::currentMigration();
$destination = $migration->getDestination();
$entity_type = $destination->getEntityType();
$bundle = $destination->getBundle();
$instances = field_info_instances($entity_type, $bundle);
foreach ($instances as $machine_name => $instance) {
if (property_exists($entity, $machine_name)) {
// Normalize to an array
if (!is_array($entity->$machine_name)) {
$entity->$machine_name = array($entity->$machine_name);
}
$field_info = field_info_field($machine_name);
$entity->$machine_name = migrate_field_handler_invoke_all($entity, $field_info,
$instance, $entity->$machine_name);
}
}
migrate_instrument_stop('MigrateDestinationEntity->prepareFields');
}
public function complete($entity, stdClass $row) {
migrate_instrument_start('MigrateDestinationEntity->completeFields');
// Look for Field API fields attached to this destination and handle appropriately
$migration = Migration::currentMigration();
$destination = $migration->getDestination();
$entity_type = $destination->getEntityType();
$bundle = $destination->getBundle();
$instances = field_info_instances($entity_type, $bundle);
foreach ($instances as $machine_name => $instance) {
if (property_exists($entity, $machine_name)) {
// Normalize to an array
if (!is_array($entity->$machine_name)) {
$entity->$machine_name = array($entity->$machine_name);
}
$field_info = field_info_field($machine_name);
migrate_field_handler_invoke_all($entity, $field_info,
$instance, $entity->$machine_name, 'complete');
}
}
migrate_instrument_stop('MigrateDestinationEntity->completeFields');
}
}
abstract class MigrateFieldHandler extends MigrateHandler {
// Derived classes are expected to implement one or both of the prepare/complete
// handlers.
// abstract public function prepare($entity, array $field_info, array $instance, array $values);
// abstract public function complete($entity, array $field_info, array $instance, array $values);
/**
* Determine the language of the field
*
* @param $entity
* @param $field_info
* @param $arguments
* @return string language code
*/
function getFieldLanguage($entity, $field_info, array $arguments) {
$migration = Migration::currentMigration();
switch (TRUE) {
case !field_is_translatable($migration->getDestination()->getEntityType(), $field_info):
return LANGUAGE_NONE;
case isset($arguments['language']):
return $arguments['language'];
case !empty($entity->language) && $entity->language != LANGUAGE_NONE:
return $entity->language;
break;
default:
return $migration->getDestination()->getLanguage();
}
}
}
/**
* Base class for creating field handlers for fields with a single value.
*
* To use this class just extend it and pass key where the field's value should
* be stored to the constructor, then register the type(s):
* @code
* class MigrateLinkFieldHandler extends MigrateSimpleFieldHandler {
* public function __construct() {
* parent::__construct('url');
* $this->registerTypes(array('link'));
* }
* }
* @endcode
*/
abstract class MigrateSimpleFieldHandler extends MigrateFieldHandler {
protected $fieldValueKey = 'value';
protected $skipEmpty = FALSE;
/**
* Construct a simple field handler.
*
* @param $options
* Array of options (rather than unamed parameters so you don't have to
* what TRUE or FALSE means). The following keys are used:
* - 'value_key' string with the name of the key in the fields value array.
* - 'skip_empty' Boolean indicating that empty values should not be saved.
*/
public function __construct($options = array()) {
if (isset($options['value_key'])) {
$this->fieldValueKey = $options['value_key'];
}
if (isset($options['skip_empty'])) {
$this->skipEmpty = $options['skip_empty'];
}
}
public function prepare($entity, array $field_info, array $instance, array $values) {
$arguments = array();
if (isset($values['arguments'])) {
$arguments = $values['arguments'];
unset($values['arguments']);
}
$language = $this->getFieldLanguage($entity, $field_info, $arguments);
// Let the derived class skip empty values.
if ($this->skipEmpty) {
$values = array_filter($values, array($this, 'notNull'));
}
// Setup the Field API array for saving.
$delta = 0;
foreach ($values as $value) {
if (is_array($language)) {
$current_language = $language[$delta];
}
else {
$current_language = $language;
}
$return[$current_language][] = array($this->fieldValueKey => $value);
$delta++;
}
return isset($return) ? $return : NULL;
}
/**
* Returns TRUE only for values which are not NULL.
*
* @param $value
* @return bool
*/
protected function notNull($value) {
return !is_null($value);
}
}
class MigrateTextFieldHandler extends MigrateFieldHandler {
public function __construct() {
$this->registerTypes(array('text', 'text_long', 'text_with_summary'));
}
static function arguments($summary = NULL, $format = NULL, $language = NULL) {
$arguments = array();
if (!is_null($summary)) {
$arguments['summary'] = $summary;
}
if (!is_null($format)) {
$arguments['format'] = $format;
}
if (!is_null($language)) {
$arguments['language'] = $language;
}
return $arguments;
}
public function fields($type) {
$fields = array();
if ($type == 'text_with_summary') {
$fields['summary'] = t('Subfield: Summary of field contents');
}
$fields += array(
'format' => t('Subfield: Text format for the field'),
'language' => t('Subfield: Language for the field'),
);
return $fields;
}
public function prepare($entity, array $field_info, array $instance, array $values) {
if (isset($values['arguments'])) {
$arguments = $values['arguments'];
unset($values['arguments']);
}
else {
$arguments = array();
}
$migration = Migration::currentMigration();
$destination = $migration->getDestination();
$language = $this->getFieldLanguage($entity, $field_info, $arguments);
$max_length = isset($field_info['settings']['max_length']) ?
$field_info['settings']['max_length'] : 0;
// Setup the standard Field API array for saving.
$delta = 0;
foreach ($values as $value) {
$item = array();
if (isset($arguments['summary'])) {
if (is_array($arguments['summary'])) {
$item['summary'] = $arguments['summary'][$delta];
}
else {
$item['summary'] = $arguments['summary'];
}
}
if (isset($arguments['format'])) {
if (is_array($arguments['format'])) {
$format = $arguments['format'][$delta];
}
else {
$format = $arguments['format'];
}
}
else {
$format = $destination->getTextFormat();
}
$item['format'] = $item['value_format'] = $format;
// Make sure the value will fit
if ($max_length) {
$item['value'] = drupal_substr($value, 0, $max_length);
if (!empty($arguments['track_overflow'])) {
$value_length = drupal_strlen($value);
if ($value_length > $max_length) {
$migration->saveMessage(
t('Value for field !field exceeds max length of !max_length, actual length is !length',
array('!field' => $instance['field_name'], '!max_length' => $max_length,
'!length' => $value_length)),
Migration::MESSAGE_INFORMATIONAL);
}
}
}
else {
$item['value'] = $value;
}
if (is_array($language)) {
$current_language = $language[$delta];
}
else {
$current_language = $language;
}
$return[$current_language][] = $item;
$delta++;
}
return isset($return) ? $return : NULL;
}
}
class MigrateValueFieldHandler extends MigrateSimpleFieldHandler {
public function __construct() {
parent::__construct(array(
'value_key' => 'value',
'skip_empty' => FALSE,
));
$this->registerTypes(array('value', 'list', 'list_boolean', 'list_integer',
'list_float', 'list_text', 'number_integer', 'number_decimal', 'number_float'));
}
}
class MigrateTaxonomyTermReferenceFieldHandler extends MigrateFieldHandler {
public function __construct() {
$this->registerTypes(array('taxonomy_term_reference'));
}
public function fields($type) {
return array(
'source_type' => t('Option: Set to \'tid\' when the value is a source ID'),
'create_term' => t('Option: Set to TRUE to create referenced terms when necessary')
);
}
public function prepare($entity, array $field_info, array $instance, array $values) {
if (isset($values['arguments'])) {
$arguments = $values['arguments'];
unset($values['arguments']);
}
else {
$arguments = array();
}
if (empty($values[0])) {
$values = array();
}
$tids = array();
if (isset($arguments['source_type']) && $arguments['source_type'] == 'tid') {
// Nothing to do. We have tids already.
$tids = $values;
}
elseif ($values) {
// Get the vocabulary for this term
if (isset($field_info['settings']['allowed_values'][0]['vid'])) {
$vid = $field_info['settings']['allowed_values'][0]['vid'];
}
else {
$vocab_name = $field_info['settings']['allowed_values'][0]['vocabulary'];
$names = taxonomy_vocabulary_get_names();
$vid = $names[$vocab_name]->vid;
}
// Cannot use taxonomy_term_load_multiple() since we have an array of names.
// It wants a singular value. This query may return case-insensitive
// matches.
$existing_terms = db_select('taxonomy_term_data', 'td')
->fields('td', array('tid', 'name'))
->condition('td.name', $values, 'IN')
->condition('td.vid', $vid)
->execute()
->fetchAllKeyed(1, 0);
foreach ($values as $value) {
if (isset($existing_terms[$value])) {
$tids[] = $existing_terms[$value];
}
elseif (!empty($arguments['create_term'])) {
$new_term = new stdClass();
$new_term->vid = $vid;
$new_term->name = $value;
taxonomy_term_save($new_term);
$tids[] = $new_term->tid;
}
}
}
$language = $this->getFieldLanguage($entity, $field_info, $arguments);
$result = array();
$delta = 0;
foreach ($tids as $tid) {
if (is_array($language)) {
$current_language = $language[$delta];
}
else {
$current_language = $language;
}
$result[$current_language][] = array('tid' => $tid);
$delta++;
}
return $result;
}
}
/**
* The next generation of file field handler. This class focuses on the file
* field itself, and offloads understanding of obtaining the actual file and
* dealing with the file entity to an embedded MigrateFileInterface instance.
*/
abstract class MigrateFileFieldBaseHandler extends MigrateFieldHandler {
/**
* Implementation of MigrateFieldHandler::fields().
*
* @param $type
* The file field type - 'file', 'image', etc.
* @param $parent_field
* Name of the parent field.
* @param Migration $migration
* The migration context for the parent field. We can look at the mappings
* and determine which subfields are relevant.
* @return array
*/
public function fields($type, $parent_field, $migration = NULL) {
$fields = array(
'file_class' => t('Option: <a href="@doc">Implementation of MigrateFile to use</a>',
array('@doc' => 'http://drupal.org/node/1540106#file_class')),
'language' => t('Subfield: Language for the field'),
);
// If we can identify the file class mapped to this field, pick up the
// subfields specific to that class.
if ($migration) {
$field_mappings = $migration->getFieldMappings();
$class_mapping = $parent_field . ':file_class';
if (isset($field_mappings[$class_mapping])) {
$mapping = $field_mappings[$class_mapping];
$file_class = $mapping->getDefaultValue();
}
}
if (!isset($file_class)) {
$file_class = 'MigrateFileUri';
}
$fields += call_user_func(array($file_class, 'fields'));
return $fields;
}
/**
* Implementation of MigrateFieldHandler::prepare().
*
* Prepare file data for saving as a Field API file field.
*
* @return array
* Field API array suitable for inserting in the destination object.
*/
public function prepare($entity, array $field_info, array $instance, array $values) {
if (isset($values['arguments'])) {
$arguments = $values['arguments'];
unset($values['arguments']);
}
else {
$arguments = array();
}
$language = $this->getFieldLanguage($entity, $field_info, $arguments);
$migration = Migration::currentMigration();
// One can override the source class via CLI or drushrc.php (the
// option is named file_function for historical reasons)
if ($migration->getOption('file_function')) {
$file_class = $migration->getOption('file_function');
}
elseif (!empty($arguments['file_class'])) {
$file_class = $arguments['file_class'];
}
else {
$file_class = 'MigrateFileUri';
}
// If a destination directory (relative to the Drupal public files directory)
// is not explicitly provided, use the default for the field.
if (empty($arguments['destination_dir'])) {
$arguments['destination_dir'] = $this->destinationDir($field_info, $instance);
}
$return = array();
$delta = 0;
// Note that what $value represents depends on the file class -
// MigrateFileUri expects a filespec/URI, MigrateFileFid expects a file ID,
// etc.
foreach ($values as $value) {
if ($value) {
// If the parent entity doesn't have an explicit uid, give ownership
// to the anonymous account
$owner = isset($entity->uid) ? $entity->uid : 0;
// Call the MigrateFileInterface implementation to do the real work
$source = new $file_class($arguments);
$file = $source->processFile($value, $owner);
// Assuming we got back a valid file ID, build the proper field
// array out of it. We assume that if we did not get back a fid, the
// MigrateFile class has saved a message indicating why.
if ($file) {
$field_array = array('fid' => $file->fid);
$return[$language][] = $this->buildFieldArray($field_array, $arguments, $delta);
}
}
$delta++;
}
return $return;
}
/**
* Determine where the migrated file should go.
*
* @param $field_info
* Field API info on the general field.
* @param $instance
* Field API info on the field instance for this entity type.
* @return string
* Directory relative to the Drupal public files directory.
*/
protected function destinationDir($field_info, $instance) {
$destination_dir = file_field_widget_uri($field_info, $instance);
return $destination_dir;
}
/**
* Add any type-specific subfields to a file field array.
*
* @param $field_array
* The field array so far (generally will just contain a fid).
* @param $arguments
* Array of arguments passed to the field handler, from which we'll extract
* our own subfields.
* @param $delta
* Index of field values being worked on, for pulling the corresponding
* subfield values if we have an array of them.
*/
abstract protected function buildFieldArray($field_array, $arguments, $delta);
}
/**
* Handle for file fields.
*/
class MigrateFileFieldHandler extends MigrateFileFieldBaseHandler {
public function __construct() {
$this->registerTypes(array('file'));
}
/**
* Implementation of MigrateFieldHandler::fields().
* Note that file and image fields support slightly different field lists.
*
* @param $type
* The file field type - 'file' or 'image'
* @param $parent_field
* Name of the parent field.
* @param Migration $migration
* The migration context for the parent field. We can look at the mappings
* and determine which subfields are relevant.
* @return array
*/
public function fields($type, $parent_field, $migration = NULL) {
$fields = parent::fields($type, $parent_field, $migration);
$fields += array(
'description' => t('Subfield: String to be used as the description value'),
'display' => t('Subfield: String to be used as the display value'),
);
return $fields;
}
/**
* Implementation of MigrateFileFieldBaseHandler::buildFieldArray().
*/
protected function buildFieldArray($field_array, $arguments, $delta) {
if (isset($arguments['description'])) {
if (is_array($arguments['description'])) {
$field_array['description'] = $arguments['description'][$delta];
}
else {
$field_array['description'] = $arguments['description'];
}
}
else {
$field_array['description'] = '';
}
if (isset($arguments['display'])) {
if (is_array($arguments['display'])) {
$field_array['display'] = $arguments['display'][$delta];
}
else {
$field_array['display'] = $arguments['display'];
}
}
else {
$field_array['display'] = 1;
}
return $field_array;
}
}
/**
* Handle for image fields;
*/
class MigrateImageFieldHandler extends MigrateFileFieldBaseHandler {
public function __construct() {
$this->registerTypes(array('image'));
}
/**
* Implementation of MigrateFieldHandler::fields().
* Note that file and image fields support slightly different field lists.
*
* @param $type
* The file field type - 'file' or 'image'
* @param $parent_field
* Name of the parent field.
* @param Migration $migration
* The migration context for the parent field. We can look at the mappings
* and determine which subfields are relevant.
* @return array
*/
public function fields($type, $parent_field, $migration = NULL) {
$fields = parent::fields($type, $parent_field, $migration);
$fields += array(
'alt' => t('Subfield: String to be used as the alt value'),
'title' => t('Subfield: String to be used as the title value'),
);
return $fields;
}
/**
* Implementation of MigrateFileFieldBaseHandler::buildFieldArray().
*/
protected function buildFieldArray($field_array, $arguments, $delta) {
if (isset($arguments['alt'])) {
if (is_array($arguments['alt'])) {
$field_array['alt'] = $arguments['alt'][$delta];
}
else {
$field_array['alt'] = $arguments['alt'];
}
}
if (isset($arguments['title'])) {
if (is_array($arguments['title'])) {
$field_array['title'] = $arguments['title'][$delta];
}
else {
$field_array['title'] = $arguments['title'];
}
}
return $field_array;
}
}
class MigrateNodeReferenceFieldHandler extends MigrateSimpleFieldHandler {
public function __construct() {
parent::__construct(array(
'value_key' => 'nid',
'skip_empty' => TRUE,
));
$this->registerTypes(array('node_reference'));
}
}
class MigrateUserReferenceFieldHandler extends MigrateSimpleFieldHandler {
public function __construct() {
parent::__construct(array(
'value_key' => 'uid',
'skip_empty' => TRUE,
));
$this->registerTypes(array('user_reference'));
}
}

View File

@@ -0,0 +1,588 @@
<?php
/**
* @file
* Support for file entity as destination. Note that File Fields have their
* own destination in fields.inc
*/
/**
* Interface for taking some value representing a file and returning
* a Drupal file entity (creating the entity if necessary).
*/
interface MigrateFileInterface {
/**
* Return a list of subfields and options specific to this implementation,
* keyed by name.
*/
public static function fields();
/**
* Create or link to a Drupal file entity.
*
* @param $value
* A class-specific value (URI, pre-existing file ID, file blob, ...)
* representing file content.
*
* @param $owner
* uid of an account to be recorded as the file owner.
*
* @return object
* File entity being created or referenced.
*/
public function processFile($value, $owner);
}
/**
* Handle the degenerate case where we already have a file ID.
*/
class MigrateFileFid implements MigrateFileInterface {
/**
* Implementation of MigrateFileInterface::fields().
*
* @return array
*/
static public function fields() {
return array();
}
/**
* Implementation of MigrateFileInterface::processFile().
*
* @param $value
* An existing file entity ID (fid).
* @param $owner
* User ID (uid) to be the owner of the file. Ignored in this case.
* @return int
* The file entity corresponding to the fid that was passed in.
*/
public function processFile($value, $owner) {
return file_load($value);
}
}
/**
* Base class for creating core file entities.
*/
abstract class MigrateFile implements MigrateFileInterface {
/**
* Extension of the core FILE_EXISTS_* constants, offering an alternative to
* reuse the existing file if present as-is (core only offers the options of
* replacing it or renaming to avoid collision).
*/
const FILE_EXISTS_REUSE = -1;
/**
* The destination directory within Drupal.
*
* @var string
*/
protected $destinationDir = 'public://';
/**
* The filename relative to destinationDir to which to save the current file.
*
* @var string
*/
protected $destinationFile = '';
/**
* How to handle destination filename collisions.
*
* @var int
*/
protected $fileReplace = FILE_EXISTS_RENAME;
/**
* Set to TRUE to prevent file deletion on rollback.
*
* @var bool
*/
protected $preserveFiles = FALSE;
/**
* An optional file object to use as a default starting point for building the
* file entity.
*
* @var stdClass
*/
protected $defaultFile;
public function __construct($arguments = array(), $default_file = NULL) {
if (isset($arguments['destination_dir'])) {
$this->destinationDir = $arguments['destination_dir'];
}
if (isset($arguments['destination_file'])) {
$this->destinationFile = $arguments['destination_file'];
}
if (isset($arguments['file_replace'])) {
$this->fileReplace = $arguments['file_replace'];
}
if (isset($arguments['preserve_files'])) {
$this->preserveFiles = $arguments['preserve_files'];
}
if ($default_file) {
$this->defaultFile = $default_file;
}
else {
$this->defaultFile = new stdClass;
}
}
/**
* Implementation of MigrateFileInterface::fields().
*
* @return array
*/
static public function fields() {
return array(
'destination_dir' => t('Subfield: <a href="@doc">Path within Drupal files directory to store file</a>',
array('@doc' => 'http://drupal.org/node/1540106#destination_dir')),
'destination_file' => t('Subfield: <a href="@doc">Path within destination_dir to store the file.</a>',
array('@doc' => 'http://drupal.org/node/1540106#destination_file')),
'file_replace' => t('Option: <a href="@doc">Value of $replace in that file function. Does not apply to file_fast(). Defaults to FILE_EXISTS_RENAME.</a>',
array('@doc' => 'http://drupal.org/node/1540106#file_replace')),
'preserve_files' => t('Option: <a href="@doc">Boolean indicating whether files should be preserved or deleted on rollback</a>',
array('@doc' => 'http://drupal.org/node/1540106#preserve_files')),
);
}
/**
* Setup a file entity object suitable for saving.
*
* @param $destination
* Path to the Drupal copy of the file.
* @param $owner
* Uid of the file owner.
* @return stdClass
* A file object ready to be saved.
*/
protected function createFileEntity($destination, $owner) {
$file = clone $this->defaultFile;
$file->uri = $destination;
$file->uid = $owner;
if (!isset($file->filename)) {
$file->filename = drupal_basename($destination);
}
if (!isset($file->filemime)) {
$file->filemime = file_get_mimetype($destination);
}
if (!isset($file->status)) {
$file->status = FILE_STATUS_PERMANENT;
}
// If we are replacing an existing file re-use its database record.
if ($this->fileReplace == FILE_EXISTS_REPLACE) {
$existing_files = file_load_multiple(array(), array('uri' => $destination));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->fid;
$file->filename = $existing->filename;
}
}
return $file;
}
/**
* By whatever appropriate means, put the file in the right place.
*
* @param $destination
* Destination path within Drupal.
* @return bool
* TRUE if the file is successfully saved, FALSE otherwise.
*/
abstract protected function copyFile($destination);
/**
* Default implementation of MigrateFileInterface::processFiles().
*
* @param $value
* The URI or local filespec of a file to be imported.
* @param $owner
* User ID (uid) to be the owner of the file.
* @return object
* The file entity being created or referenced.
*/
public function processFile($value, $owner) {
$migration = Migration::currentMigration();
// Determine the final path we want in Drupal - start with our preferred path.
$destination = file_stream_wrapper_uri_normalize(
$this->destinationDir . '/' .
ltrim($this->destinationFile, "/\\"));
// Our own file_replace behavior - if the file exists, use it without
// replacing it
if ($this->fileReplace == self::FILE_EXISTS_REUSE) {
// See if we this file already (we'll reuse a file entity if it exists).
if (file_exists($destination)) {
$file = $this->createFileEntity($destination, $owner);
// File entity didn't already exist, create it
if (empty($file->fid)) {
$file = file_save($file);
}
return $file;
}
// No existing one to reuse, reset to REPLACE
$this->fileReplace = FILE_EXISTS_REPLACE;
}
// Prepare the destination directory.
if (!file_prepare_directory(drupal_dirname($destination),
FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
$migration->saveMessage(t('Could not create destination directory for !dest',
array('!dest' => $destination)));
return FALSE;
}
// Determine whether we can perform this operation based on overwrite rules.
$destination = file_destination($destination, $this->fileReplace);
if ($destination === FALSE) {
$migration->saveMessage(t('The file could not be copied because ' .
'file %dest already exists in the destination directory.',
array('%dest' => $destination)));
return FALSE;
}
// Make sure the .htaccess files are present.
file_ensure_htaccess();
// Put the file where it needs to be.
if (!$this->copyFile($destination)) {
return FALSE;
}
// Set the permissions on the new file.
drupal_chmod($destination);
// Create and save the file entity.
$file = file_save($this->createFileEntity($destination, $owner));
// Prevent deletion of the file on rollback if requested.
if (is_object($file)) {
if (!empty($this->preserveFiles)) {
// We do this directly instead of calling file_usage_add, to force the
// count to 1 - otherwise, updates will increment the counter and the file
// will never be deletable
db_merge('file_usage')
->key(array(
'fid' => $file->fid,
'module' => 'migrate',
'type' => 'file',
'id' => $file->fid,
))
->fields(array('count' => 1))
->execute();
}
return $file;
}
else {
return FALSE;
}
}
}
/**
* Handle cases where we're handed a URI, or local filespec, representing a file
* to be imported to Drupal.
*/
class MigrateFileUri extends MigrateFile {
/**
* The source directory for the file, relative to which the value (source
* file) will be taken.
*
* @var string
*/
protected $sourceDir = '';
/**
* The full path to the source file.
*
* @var string
*/
protected $sourcePath = '';
public function __construct($arguments = array(), $default_file = NULL) {
parent::__construct($arguments, $default_file);
if (isset($arguments['source_dir'])) {
$this->sourceDir = rtrim($arguments['source_dir'], "/\\");
}
}
/**
* Implementation of MigrateFileInterface::fields().
*
* @return array
*/
static public function fields() {
return parent::fields() +
array(
'source_dir' => t('Subfield: <a href="@doc">Path to source file.</a>',
array('@doc' => 'http://drupal.org/node/1540106#source_dir')),
);
}
/**
* Implementation of MigrateFile::copyFile().
*
* @param $destination
* Destination within Drupal.
*
* @return bool
* TRUE if the copy succeeded, FALSE otherwise.
*/
protected function copyFile($destination) {
// Perform the copy operation.
if (!@copy($this->sourcePath, $destination)) {
throw new MigrateException(t('The specified file %file could not be copied to ' .
'%destination.',
array('%file' => $this->sourcePath, '%destination' => $destination)));
}
else {
return TRUE;
}
}
/**
* Implementation of MigrateFileInterface::processFiles().
*
* @param $value
* The URI or local filespec of a file to be imported.
* @param $owner
* User ID (uid) to be the owner of the file.
* @return object
* The file entity being created or referenced.
*/
public function processFile($value, $owner) {
// Identify the full path to the source file
if (!empty($this->sourceDir)) {
$this->sourcePath = rtrim($this->sourceDir, "/\\") . '/' . ltrim($value, "/\\");
}
else {
$this->sourcePath = $value;
}
if (empty($this->destinationFile)) {
$this->destinationFile = basename($this->sourcePath);
}
// MigrateFile has most of the smarts - the key is that it will call back
// to our copyFile() implementation.
$file = parent::processFile($value, $owner);
return $file;
}
}
/**
* Handle cases where we're handed a blob (i.e., the actual contents of a file,
* such as image data) to be stored as a real file in Drupal.
*/
class MigrateFileBlob extends MigrateFile {
/**
* The file contents we will be writing to a real file.
*
* @var
*/
protected $fileContents;
/**
* Implementation of MigrateFile::copyFile().
*
* @param $destination
* Drupal destination path.
* @return bool
* TRUE if the file contents were successfully written, FALSE otherwise.
*/
protected function copyFile($destination) {
if (file_put_contents($destination, $this->fileContents)) {
return TRUE;
}
else {
$migration = Migration::currentMigration();
$migration->saveMessage(t('Failed to write blob data to %destination',
array('%destination' => $destination)));
return FALSE;
}
}
/**
* Implementation of MigrateFileInterface::processFile().
*
* @param $value
* The file contents to be saved as a file.
* @param $owner
* User ID (uid) to be the owner of the file.
* @return object
* File entity being created or referenced.
*/
public function processFile($value, $owner) {
$this->fileContents = $value;
$file = parent::processFile($value, $owner);
return $file;
}
}
/**
* Destination class implementing migration into the files table.
*/
class MigrateDestinationFile extends MigrateDestinationEntity {
/**
* File class (MigrateFileUri etc.) doing the dirty wrk.
*
* @var string
*/
protected $fileClass;
/**
* Implementation of MigrateDestination::getKeySchema().
*
* @return array
*/
static public function getKeySchema() {
return array(
'fid' => array(
'type' => 'int',
'unsigned' => TRUE,
'description' => 'file_managed ID',
),
);
}
/**
* Basic initialization
*
* @param array $options
* Options applied to files.
*/
public function __construct($bundle = 'file', $file_class = 'MigrateFileUri',
$options = array()) {
parent::__construct('file', $bundle, $options);
$this->fileClass = $file_class;
}
/**
* Returns a list of fields available to be mapped for the entity type (bundle)
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array();
// First the core properties
$fields['fid'] = t('File: Existing file ID');
$fields['uid'] = t('File: Uid of user associated with file');
$fields['value'] = t('File: Representation of the source file (usually a URI)');
$fields['timestamp'] = t('File: UNIX timestamp for the date the file was added');
// Then add in anything provided by handlers
$fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle, $migration);
$fields += migrate_handler_invoke_all('File', 'fields', $this->entityType, $this->bundle, $migration);
// Plus anything provided by the file class
$fields += call_user_func(array($this->fileClass, 'fields'));
return $fields;
}
/**
* Delete a file entry.
*
* @param array $fid
* Fid to delete, arrayed.
*/
public function rollback(array $fid) {
migrate_instrument_start('file_load');
$file = file_load(reset($fid));
migrate_instrument_stop('file_load');
if ($file) {
// If we're not preserving the file, make sure we do the job completely.
migrate_instrument_start('file_delete');
file_delete($file, TRUE);
migrate_instrument_stop('file_delete');
}
}
/**
* Import a single file record.
*
* @param $file
* File object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields (fid only in this case) of the file that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $file, stdClass $row) {
// Updating previously-migrated content?
$migration = Migration::currentMigration();
if (isset($row->migrate_map_destid1)) {
if (isset($file->fid)) {
if ($file->fid != $row->migrate_map_destid1) {
throw new MigrateException(t("Incoming fid !fid and map destination fid !destid1 don't match",
array('!fid' => $file->fid, '!destid1' => $row->migrate_map_destid1)));
}
}
else {
$file->fid = $row->migrate_map_destid1;
}
}
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
if (!isset($file->fid)) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination fid provided'));
}
$old_file = file_load($file->fid);
}
// Invoke migration prepare handlers
$this->prepare($file, $row);
if (isset($file->fid)) {
$updating = TRUE;
}
else {
$updating = FALSE;
}
if (!isset($file->uid)) {
$file->uid = 1;
}
// file_save() unconditionally sets timestamp - if we have an explicit
// value we want, we need to set it manually after file_save.
if (isset($file->timestamp)) {
$timestamp = MigrationBase::timestamp($file->timestamp);
}
$file_class = $this->fileClass;
$source = new $file_class((array)$file, $file);
$file = $source->processFile($file->value, $file->uid);
if (is_object($file) && isset($file->fid)) {
$this->complete($file, $row);
if (isset($timestamp)) {
db_update('file_managed')
->fields(array('timestamp' => $timestamp))
->condition('fid', $file->fid)
->execute();
$file->timestamp = $timestamp;
}
$return = array($file->fid);
if ($updating) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
}
else {
$return = FALSE;
}
return $return;
}
}

View File

@@ -0,0 +1,188 @@
<?php
/**
* @file
* Support for menu destinations.
*/
/**
* Destination class implementing migration into {menu_custom}.
*/
class MigrateDestinationMenu extends MigrateDestination {
static public function getKeySchema() {
return array(
'menu_name' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => 'Primary Key: Unique key for menu. This is used as a block delta so length is 32.',
),
);
}
public function __construct() {
parent::__construct();
}
public function __toString() {
$output = t('Menu');
return $output;
}
/**
* Returns a list of fields available to be mapped for menus.
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array(
'menu_name' => t('The menu name. Primary key.'),
'title' => t('The human-readable name of the menu.'),
'description' => t('A description of the menu'),
);
return $fields;
}
/**
* Import a single row.
*
* @param $menu
* Menu object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields of the object that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $menu, stdClass $row) {
// Invoke migration prepare handlers
$this->prepare($menu, $row);
// Menus are handled as arrays, so clone the object to an array.
$menu = clone $menu;
$menu = (array) $menu;
// Check to see if this is a new menu.
$update = FALSE;
if ($data = menu_load($menu['menu_name'])) {
$update = TRUE;
}
// menu_save() provides no return callback, so we can't really test this
// without running a menu_load() check.
migrate_instrument_start('menu_save');
menu_save($menu);
migrate_instrument_stop('menu_save');
// Return the new id or FALSE on failure.
if ($data = menu_load($menu['menu_name'])) {
// Increment the count if the save succeeded.
if ($update) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
// Return the primary key to the mapping table.
$return = array($data['menu_name']);
}
else {
$return = FALSE;
}
// Invoke migration complete handlers.
$menu = (object) $data;
$this->complete($menu, $row);
return $return;
}
/**
* Implementation of MigrateDestination::prepare().
*/
public function prepare($menu, stdClass $row) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
$menu->migrate = array(
'machineName' => $migration->getMachineName(),
);
// Call any general handlers.
migrate_handler_invoke_all('menu', 'prepare', $menu, $row);
// Then call any prepare handler for this specific Migration.
if (method_exists($migration, 'prepare')) {
$migration->prepare($menu, $row);
}
}
public function complete($menu, stdClass $row) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
$menu->migrate = array(
'machineName' => $migration->getMachineName(),
);
// Call any general handlers.
migrate_handler_invoke_all('menu', 'complete', $menu, $row);
// Then call any complete handler for this specific Migration.
if (method_exists($migration, 'complete')) {
$migration->complete($menu, $row);
}
}
/**
* Delete a single menu.
*
* @param $id
* Array of fields representing the key (in this case, just menu_name).
*/
public function rollback(array $id) {
$menu_name = reset($id);
migrate_instrument_start('menu_delete');
$this->prepareRollback($menu_name);
if ($menu = menu_load($menu_name)) {
menu_delete($menu);
}
$this->completeRollback($menu_name);
migrate_instrument_stop('menu_delete');
}
/**
* Give handlers a shot at cleaning up before a menu has been rolled back.
*
* @param $menu_name
* ID of the menu about to be deleted.
*/
public function prepareRollback($menu_name) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
// Call any general handlers.
migrate_handler_invoke_all('menu', 'prepareRollback', $menu_name);
// Then call any complete handler for this specific Migration.
if (method_exists($migration, 'prepareRollback')) {
$migration->prepareRollback($menu_name);
}
}
/**
* Give handlers a shot at cleaning up after a menu has been rolled back.
*
* @param $menu_name
* ID of the menu which has been deleted.
*/
public function completeRollback($menu_name) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
// Call any general handlers.
migrate_handler_invoke_all('menu', 'completeRollback', $menu_name);
// Then call any complete handler for this specific Migration.
if (method_exists($migration, 'completeRollback')) {
$migration->completeRollback($menu_name);
}
}
}

View File

@@ -0,0 +1,235 @@
<?php
/**
* @file
* Support for menu link destinations.
*/
/**
* Destination class implementing migration into {menu_links}.
*/
class MigrateDestinationMenuLinks extends MigrateDestination {
static public function getKeySchema() {
return array(
'mlid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'ID of destination link',
),
);
}
public function __construct() {
parent::__construct();
}
public function __toString() {
$output = t('Menu links');
return $output;
}
/**
* Returns a list of fields available to be mapped for menu links.
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array(
'menu_name' => t('The menu name. All links with the same menu name (such as \'navigation\') are part of the same menu.'),
'mlid' => t('The menu link ID (mlid) is the integer primary key.'),
'plid' => t('The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.'),
'link_path' => t('The Drupal path or external path this link points to.'),
'router_path' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.'),
'link_title' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
'options' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'),
'module' => t('The name of the module that generated this link.'),
'hidden' => t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)'),
'external' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'),
'has_children' => t('Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).'),
'expanded' => t('Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)'),
'weight' => t('Link weight among links in the same menu at the same depth.'),
'depth' => t('The depth relative to the top level. A link with plid == 0 will have depth == 1.'),
'customized' => t('A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).'),
'p1' => t('The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.'),
'p2' => t('The second mlid in the materialized path. See p1.'),
'p3' => t('The third mlid in the materialized path. See p1.'),
'p4' => t('The fourth mlid in the materialized path. See p1.'),
'p5' => t('The fifth mlid in the materialized path. See p1.'),
'p6' => t('The sixth mlid in the materialized path. See p1.'),
'p7' => t('The seventh mlid in the materialized path. See p1.'),
'p8' => t('The eighth mlid in the materialized path. See p1.'),
'p9' => t('The ninth mlid in the materialized path. See p1.'),
'updated' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
);
return $fields;
}
/**
* Import a single row.
*
* @param $menu_link
* Menu link object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields of the object that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $menu_link, stdClass $row) {
// Updating previously-migrated content
if (isset($row->migrate_map_destid1)) {
$menu_link->mlid = $row->migrate_map_destid1;
}
// Invoke migration prepare handlers
// @todo derive existing mlids?
$this->prepare($menu_link, $row);
// Menu links are handled as arrays, so clone the object to an array.
$item = clone $menu_link;
$item = (array) $item;
migrate_instrument_start('menu_link_save');
// Check to see if this is a new menu item.
$update = FALSE;
if (isset($item['mlid'])) {
$update = TRUE;
$mlid = menu_link_save($item);
}
else {
// menu_link_save() should return an mlid integer.
$mlid = menu_link_save($item);
}
migrate_instrument_stop('menu_link_save');
// Return the new id or FALSE on failure.
if (!empty($mlid)) {
// Increment the count if the save succeeded.
if ($update) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
// Return the primary key to the mapping table.
$return = array($mlid);
}
else {
$return = FALSE;
}
// Invoke migration complete handlers.
$menu_link = (object) menu_link_load($mlid);
$this->complete($menu_link, $row);
return $return;
}
/**
* Implementation of MigrateDestination::prepare().
*/
public function prepare($menu_link, stdClass $row) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
$menu_link->migrate = array(
'machineName' => $migration->getMachineName(),
);
// Call any general handlers.
migrate_handler_invoke_all('menu_links', 'prepare', $menu_link, $row);
// Then call any prepare handler for this specific Migration.
if (method_exists($migration, 'prepare')) {
$migration->prepare($menu_link, $row);
}
}
/**
* Implementation of MigrateDestination::complete().
*/
public function complete($menu_link, stdClass $row) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
$menu_link->migrate = array(
'machineName' => $migration->getMachineName(),
);
// Call any general handlers.
migrate_handler_invoke_all('menu_links', 'complete', $menu_link, $row);
// Then call any complete handler for this specific Migration.
if (method_exists($migration, 'complete')) {
$migration->complete($menu_link, $row);
}
}
/**
* Implementation of MigrateDestination::postImport().
*/
public function postImport() {
// Clear the cache after all menu links are imported.
menu_cache_clear_all();
}
/**
* Delete a single menu item.
*
* @param $id
* Array of fields representing the key (in this case, just mlid).
*/
public function rollback($id) {
$mlid = reset($id);
migrate_instrument_start('menu_link_delete');
$this->prepareRollback($mlid);
// @todo: any error checking here? For example, menu.inc has:
// if ($menu = menu_load($menu_name)) { menu_delete($menu) }
menu_link_delete($mlid);
$this->completeRollback($mlid);
migrate_instrument_stop('menu_link_delete');
}
/**
* Give handlers a shot at cleaning up before a menu has been rolled back.
*
* @param $mlid
* ID of the menu link about to be deleted.
*/
public function prepareRollback($mlid) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
// Call any general handlers.
migrate_handler_invoke_all('menu_links', 'prepareRollback', $mlid);
// Then call any complete handler for this specific Migration.
if (method_exists($migration, 'prepareRollback')) {
$migration->prepareRollback($mlid);
}
}
/**
* Give handlers a shot at cleaning up after a menu has been rolled back.
*
* @param $mlid
* ID of the menu link which has been deleted.
*/
public function completeRollback($mlid) {
// We do nothing here but allow child classes to act.
$migration = Migration::currentMigration();
// Call any general handlers.
migrate_handler_invoke_all('menu_links', 'completeRollback', $mlid);
// Then call any complete handler for this specific Migration.
if (method_exists($migration, 'completeRollback')) {
$migration->completeRollback($mlid);
}
}
/**
* Implementation of MigrateDestination::postRollback().
*/
public function postRollback() {
// Clear the cache after all menu links are rolled back.
menu_cache_clear_all();
}
}

View File

@@ -0,0 +1,296 @@
<?php
/**
* @file
* Support for node destinations.
*/
// TODO:
// Make sure this works with updates, explicit destination keys
/**
* Destination class implementing migration into nodes.
*/
class MigrateDestinationNode extends MigrateDestinationEntity {
static public function getKeySchema() {
return array(
'nid' => array(
'type' => 'int',
'unsigned' => TRUE,
'description' => 'ID of destination node',
),
);
}
/**
* Return an options array for node destinations.
*
* @param string $language
* Default language for nodes created via this destination class.
* @param string $text_format
* Default text format for nodes created via this destination class.
*/
static public function options($language, $text_format) {
return compact('language', 'text_format');
}
/**
* Basic initialization
*
* @param string $bundle
* A.k.a. the content type (page, article, etc.) of the node.
* @param array $options
* Options applied to nodes.
*/
public function __construct($bundle, array $options = array()) {
parent::__construct('node', $bundle, $options);
}
/**
* Returns a list of fields available to be mapped for the node type (bundle)
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array();
// First the core (node table) properties
$fields['nid'] = t('Node: <a href="@doc">Existing node ID</a>',
array('@doc' => 'http://drupal.org/node/1349696#nid'));
$node_type = node_type_load($this->bundle);
if ($node_type->has_title) {
$fields['title'] = t('Node: <a href="@doc">',
array('@doc' => 'http://drupal.org/node/1349696#title'))
. $node_type->title_label . '</a>';
}
$fields['uid'] = t('Node: <a href="@doc">Authored by (uid)</a>',
array('@doc' => 'http://drupal.org/node/1349696#uid'));
$fields['created'] = t('Node: <a href="@doc">Created timestamp</a>',
array('@doc' => 'http://drupal.org/node/1349696#created'));
$fields['changed'] = t('Node: <a href="@doc">Modified timestamp</a>',
array('@doc' => 'http://drupal.org/node/1349696#changed'));
$fields['status'] = t('Node: <a href="@doc">Published</a>',
array('@doc' => 'http://drupal.org/node/1349696#status'));
$fields['promote'] = t('Node: <a href="@doc">Promoted to front page</a>',
array('@doc' => 'http://drupal.org/node/1349696#promote'));
$fields['sticky'] = t('Node: <a href="@doc">Sticky at top of lists</a>',
array('@doc' => 'http://drupal.org/node/1349696#sticky'));
$fields['revision'] = t('Node: <a href="@doc">Create new revision</a>',
array('@doc' => 'http://drupal.org/node/1349696#revision'));
$fields['log'] = t('Node: <a href="@doc">Revision Log message</a>',
array('@doc' => 'http://drupal.org/node/1349696#log'));
$fields['language'] = t('Node: <a href="@doc">Language (fr, en, ...)</a>',
array('@doc' => 'http://drupal.org/node/1349696#language'));
$fields['tnid'] = t('Node: <a href="@doc">The translation set id for this node</a>',
array('@doc' => 'http://drupal.org/node/1349696#tnid'));
$fields['revision_uid'] = t('Node: <a href="@doc">Modified (uid)</a>',
array('@doc' => 'http://drupal.org/node/1349696#revision_uid'));
$fields['is_new'] = t('Option: <a href="@doc">Indicates a new node with the specified nid should be created</a>',
array('@doc' => 'http://drupal.org/node/1349696#is_new'));
// Then add in anything provided by handlers
$fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle, $migration);
$fields += migrate_handler_invoke_all('Node', 'fields', $this->entityType, $this->bundle, $migration);
return $fields;
}
/**
* Delete a batch of nodes at once.
*
* @param $nids
* Array of node IDs to be deleted.
*/
public function bulkRollback(array $nids) {
migrate_instrument_start('node_delete_multiple');
$this->prepareRollback($nids);
node_delete_multiple($nids);
$this->completeRollback($nids);
migrate_instrument_stop('node_delete_multiple');
}
/**
* Import a single node.
*
* @param $node
* Node object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields (nid only in this case) of the node that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $node, stdClass $row) {
// Updating previously-migrated content?
$migration = Migration::currentMigration();
if (isset($row->migrate_map_destid1)) {
// Make sure is_new is off
$node->is_new = FALSE;
if (isset($node->nid)) {
if ($node->nid != $row->migrate_map_destid1) {
throw new MigrateException(t("Incoming nid !nid and map destination nid !destid1 don't match",
array('!nid' => $node->nid, '!destid1' => $row->migrate_map_destid1)));
}
}
else {
$node->nid = $row->migrate_map_destid1;
}
// Get the existing vid, tnid so updates don't generate notices
$values = db_select('node', 'n')
->fields('n', array('vid', 'tnid'))
->condition('nid', $node->nid)
->execute()
->fetchAssoc();
if (empty($values)) {
throw new MigrateException(t("Incoming node ID !nid no longer exists",
array('!nid' => $node->nid)));
}
$node->vid = $values['vid'];
if (empty($row->tnid)) {
$node->tnid = $values['tnid'];
}
}
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
if (!isset($node->nid)) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination nid provided'));
}
$old_node = node_load($node->nid);
if (empty($old_node)) {
throw new MigrateException(t('System-of-record is DESTINATION, but node !nid does not exist',
array('!nid' => $node->nid)));
}
if (!isset($node->created)) {
$node->created = $old_node->created;
}
if (!isset($node->vid)) {
$node->vid = $old_node->vid;
}
if (!isset($node->status)) {
$node->status = $old_node->status;
}
if (!isset($node->uid)) {
$node->uid = $old_node->uid;
}
}
elseif (!isset($node->type)) {
// Default the type to our designated destination bundle (by doing this
// conditionally, we permit some flexibility in terms of implementing
// migrations which can affect more than one type).
$node->type = $this->bundle;
}
// Set some required properties.
if ($migration->getSystemOfRecord() == Migration::SOURCE) {
if (!isset($node->language)) {
$node->language = $this->language;
}
// Apply defaults, allow standard node prepare hooks to fire.
// node_object_prepare() will blow these away, so save them here and
// stuff them in later if need be.
if (isset($node->created)) {
$created = MigrationBase::timestamp($node->created);
}
else {
// To keep node_object_prepare() from choking
$node->created = REQUEST_TIME;
}
if (isset($node->changed)) {
$changed = MigrationBase::timestamp($node->changed);
}
if (isset($node->uid)) {
$uid = $node->uid;
}
node_object_prepare($node);
if (isset($created)) {
$node->created = $created;
}
// No point to resetting $node->changed here, node_save() will overwrite it
if (isset($uid)) {
$node->uid = $uid;
}
}
// Invoke migration prepare handlers
$this->prepare($node, $row);
if (!isset($node->revision)) {
$node->revision = 0; // Saves disk space and writes. Can be overridden.
}
// Trying to update an existing node
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
// Incoming data overrides existing data, so only copy non-existent fields
foreach ($old_node as $field => $value) {
// An explicit NULL in the source data means to wipe to old value (i.e.,
// don't copy it over from $old_node)
if (property_exists($node, $field) && $node->$field === NULL) {
// Ignore this field
}
elseif (!isset($node->$field)) {
$node->$field = $old_node->$field;
}
}
}
if (isset($node->nid) && !(isset($node->is_new) && $node->is_new)) {
$updating = TRUE;
}
else {
$updating = FALSE;
}
migrate_instrument_start('node_save');
node_save($node);
migrate_instrument_stop('node_save');
if (isset($node->nid)) {
if ($updating) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
// Unfortunately, http://drupal.org/node/722688 was not accepted, so fix
// the changed timestamp
if (isset($changed)) {
db_update('node')
->fields(array('changed' => $changed))
->condition('nid', $node->nid)
->execute();
$node->changed = $changed;
}
// Potentially fix uid and timestamp in node_revisions.
$query = db_update('node_revision')
->condition('vid', $node->vid);
if (isset($changed)) {
$fields['timestamp'] = $changed;
}
$revision_uid = isset($node->revision_uid) ? $node->revision_uid : $node->uid;
if ($revision_uid != $GLOBALS['user']->uid) {
$fields['uid'] = $revision_uid;
}
if (!empty($fields)) {
// We actually have something to update.
$query->fields($fields);
$query->execute();
if (isset($changed)) {
$node->timestamp = $changed;
}
}
$return = array($node->nid);
}
else {
$return = FALSE;
}
$this->complete($node, $row);
return $return;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* @file
* Support for paths in core Drupal objects
*/
class MigratePathEntityHandler extends MigrateDestinationHandler {
public function __construct() {
$this->registerTypes(array('entity'));
}
public function fields() {
if (module_exists('path')) {
return array('path' => t('Node: Path alias'));
}
else {
return array();
}
}
public function prepare($entity, stdClass $row) {
if (module_exists('path') && isset($entity->path)) {
$path = $entity->path;
$entity->path = array();
$entity->path['alias'] = $path;
}
}
}

View File

@@ -0,0 +1,121 @@
<?php
/**
* @file
* Support for poll nodes.
*
* Each poll node will have multiple choices, and multiple votes. It's
* not practical to bring this information in through your migration's
* source query, you need to pull it separately in prepareRow():
*
* @code
* ...
* $this->addFieldMapping('active')
* ->defaultValue(1);
* $this->addFieldMapping('runtime', 'seconds_to_run')
* $this->addFieldMapping('choice', 'src_choices')
* ->description('src_choices populated in prepareRow()');
* $this->addFieldMapping('votes', 'src_votes')
* ->description('src_votes populated in prepareRow()');
* ...
* public function prepareRow($row);
* $choices = Database::getConnection('default', 'legacy')
* ->select('src_poll_choice', 'c')
* ->fields('c', array('choice_label', 'choice_order', 'choice_total))
* ->condition('c.choiceid', $row->src_contentid);
* ->execute();
* $row->src_choices = array();
* foreach ($choices as $choice) {
* $row->src_choices[] = array(
* 'chtext' => $choice->choice_label,
* 'chvotes' => $choice->choice_total,
* 'weight' => $choice->choice_order,
* );
* }
* // Note that we won't know until much later what the chid is for each
* // choice, so it's best to tie the votes to choices by text.
* $query = Database::getConnection('default', 'legacy')
* ->select('src_poll_vote', 'v')
* ->fields('v', array('choice_uid', 'hostname', 'timestamp))
* ->condition('v.choiceid', $row->src_contentid);
* $votes = $query->innerJoin('src_poll_choice', 'c', 'v.choice_id=c.choice_id')
* ->fields('c', array('choice_label'))
* ->execute();
* $row->src_votes = array();
* foreach ($votes as $vote) {
* $row->src_votes[] = array(
* 'chtext' => $choice->choice_label,
* 'uid' => $choice->choice_uid,
* 'hostname' => $choice->hostname,
* 'timestamp' => $choice->timestamp,
* );
* }
* return TRUE;
* }
* @endcode
*/
class MigratePollEntityHandler extends MigrateDestinationHandler {
public function __construct() {
$this->registerTypes(array('node'));
}
public function fields($entity_type, $bundle) {
if ($bundle == 'poll') {
$fields = array(
'active' => t('Poll: Active status'),
'runtime' => t('Poll: How long the poll runs for in seconds'),
'choice' => t('Poll: Choices. Each choice is an array with chtext, chvotes, and weight keys.'),
'votes' => t('Poll: Votes. Each vote is an array with chid (or chtext), uid, hostname, and timestamp keys'),
);
}
else {
$fields = array();
}
return $fields;
}
public function complete($entity, stdClass $row) {
if ($entity->type == 'poll') {
// Update settings overridden by !user_access('administer nodes') check in
// poll_insert().
db_update('poll')
->fields(array('active' => $entity->active))
->condition('nid', $entity->nid)
->execute();
// Update vote summary count, again overridden by
// !user_access('administer nodes') check in poll_insert().
foreach ($row->choice as $choice) {
// Have no mapping tracking for chid, so assume choice text is unique.
db_update('poll_choice')
->fields(array('chvotes' => $choice['chvotes'], 'weight' => $choice['weight']))
->condition('nid', $entity->nid)
->condition('chtext', $choice['chtext'])
->execute();
}
// Insert actual votes.
foreach ($row->votes as $vote) {
$chid = $vote['chid'];
if (!isset($chid)) {
$result = db_select('poll_choice', 'pc')
->fields('pc', array('chid'))
->condition('pc.nid', $entity->nid)
->condition('pc.chtext', $vote['chtext'])
->execute();
$chid = $result->fetchField();
}
db_insert('poll_vote')
->fields(array(
'chid' => $chid,
'nid' => $entity->nid,
'uid' => $vote['uid'],
'hostname' => $vote['hostname'],
'timestamp' => $vote['timestamp'],
))
->execute();
}
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* @file
* Support for node_counter statistics in core Drupal nodes.
*/
class MigrateStatisticsEntityHandler extends MigrateDestinationHandler {
public function __construct() {
$this->registerTypes(array('node'));
}
public function fields() {
if (module_exists('statistics')) {
$fields = array(
'totalcount' => t('Node: The total number of times the node has been viewed.'),
'daycount' => t('Node: The total number of times the node has been viewed today.'),
'timestamp' => t('Node: The most recent time the node has been viewed.'),
);
}
else {
$fields = array();
}
return $fields;
}
public function complete($node, stdClass $row) {
if (module_exists('statistics') && isset($node->nid)) {
$totalcount = isset($node->totalcount) ? $node->totalcount : 0;
$daycount = isset($node->daycount) ? $node->daycount : 0;
$timestamp = isset($node->timestamp) ? $node->timestamp : 0;
db_merge('node_counter')
->key(array('nid' => $node->nid))
->fields(array(
'totalcount' => $totalcount,
'daycount' => $daycount,
'timestamp' => $timestamp,
))
->execute();
}
}
}

View File

@@ -0,0 +1,210 @@
<?php
/**
* @file
* Support for tables defined through the Schema API.
*/
/**
* Destination class implementing migration into a single table defined through
* the Schema API.
*/
class MigrateDestinationTable extends MigrateDestination {
/**
* The schema of the current table.
*
* @var array
*/
protected $schema = NULL;
/**
* The name of the current table.
*
* @var string
*/
protected $tableName = NULL;
public function __construct($table_name) {
$this->schema = drupal_get_schema($table_name);
$this->tableName = $table_name;
}
static public function getKeySchema($table_name = NULL) {
if (empty($table_name)) {
return array();
}
$schema = drupal_get_schema($table_name);
$keys = array();
foreach ($schema['primary key'] as $primary_key) {
// We can't have any form of serial fields here, since the mapping table
// already has it's own.
$schema['fields'][$primary_key]['auto_increment'] = FALSE;
if ($schema['fields'][$primary_key]['type'] == 'serial') {
$schema['fields'][$primary_key]['type'] = 'int';
}
$keys[$primary_key] = $schema['fields'][$primary_key];
}
return $keys;
}
public function __toString() {
$output = t('Table !name', array('!name' => $this->tableName));
return $output;
}
/**
* Delete a single row.
*
* @param $id
* Primary key values.
*/
public function rollback(array $id) {
migrate_instrument_start('table rollback');
$delete = db_delete($this->tableName);
$keys = array_keys(self::getKeySchema($this->tableName));
$i = 0;
foreach ($id as $value) {
$key = $keys[$i++];
$delete->condition($key, $value);
}
$delete->execute();
migrate_instrument_stop('table rollback');
}
/**
* Import a single row.
*
* @param $entity
* Object object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields of the object that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $entity, stdClass $row) {
if (empty($this->schema['primary key'])) {
throw new MigrateException(t("The destination table has no primary key defined."));
}
// Only filled when doing an update.
$primary_key = array();
$migration = Migration::currentMigration();
// Updating previously-migrated content?
if (isset($row->migrate_map_destid1)) {
$i = 1;
foreach ($this->schema['primary key'] as $key) {
$primary_key[] = $key;
$destination_id = $row->{'migrate_map_destid' . $i};
if (isset($entity->{$key})) {
if ($entity->{$key} != $destination_id) {
throw new MigrateException(t("Incoming id !id and map destination id !destid don't match",
array('!id' => $entity->{$key}, '!destid' => $destination_id)));
}
}
else {
$entity->{$key} = $destination_id;
}
$i++;
}
}
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
foreach ($this->schema['primary key'] as $key) {
$primary_key[] = $key;
if (!isset($entity->{$key})) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination id provided'));
}
}
$select = db_select($this->tableName)
->fields($this->tableName);
foreach ($this->schema['primary key'] as $key) {
$select->condition($key, $entity->{$key});
}
$old_entity = $select->execute()->fetchObject();
if (empty($old_entity)) {
throw new MigrateException(t('System-of-record is DESTINATION, but the destination entity does not exist'));
}
foreach ($entity as $field => $value) {
$old_entity->$field = $entity->$field;
}
$entity = $old_entity;
}
$this->prepare($entity, $row);
$status = drupal_write_record($this->tableName, $entity, $primary_key);
$this->complete($entity, $row);
if ($status) {
$id = array();
foreach ($this->schema['primary key'] as $key) {
$id[] = $entity->{$key};
}
// Increment the number of updated or inserted records by checking the
// result of drupal_write_record.
($status == SAVED_NEW) ? $this->numCreated++ : $this->numUpdated++;
return $id;
}
}
/**
* Returns a list of fields available to be mapped.
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array();
foreach ($this->schema['fields'] as $column => $schema) {
$fields[$column] = t('Type: !type', array('!type' => $schema['type']));
}
return $fields;
}
/**
* Give handlers a shot at modifying the object before saving it.
*
* @param $entity
* Entity object to build. Prefilled with any fields mapped in the Migration.
* @param $source_row
* Raw source data object - passed through to prepare handlers.
*/
public function prepare($entity, stdClass $source_row) {
$migration = Migration::currentMigration();
$entity->migrate = array(
'machineName' => $migration->getMachineName(),
);
// Call any prepare handler for this specific Migration.
if (method_exists($migration, 'prepare')) {
$migration->prepare($entity, $source_row);
}
}
/**
* Give handlers a shot at modifying the object (or taking additional action)
* after saving it.
*
* @param $object
* Entity object to build. This is the complete object after saving.
* @param $source_row
* Raw source data object - passed through to complete handlers.
*/
public function complete($entity, stdClass $source_row) {
$migration = Migration::currentMigration();
// Call any complete handler for this specific Migration.
if (method_exists($migration, 'complete')) {
$migration->complete($entity, $source_row);
}
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* @file
* Copies data_row into a table using drupal_write_record()
*/
/**
* Destination class implementing migration into a single table.
*/
class MigrateDestinationTableCopy extends MigrateDestination {
public function __construct($tableName, $keySchema) {
parent::__construct();
$this->tableName = $tableName;
$this->keySchema = $keySchema;
}
public function __toString() {
$output = t('Table copy');
return $output;
}
/**
* Delete a batch of rows at once.
*
* @param $ids
* Array of IDs to be deleted.
*/
public function bulkRollback(array $ids) {
migrate_instrument_start('table_copy bulkRollback');
db_delete($this->tableName)
->condition(key($this->keySchema), $ids, 'IN')
->execute();
migrate_instrument_stop('table_copy bulkRollback');
}
/**
* Import a single row.
*
* @param $entity
* Object object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields of the object that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $entity, stdClass $row) {
$migration = MigrationBase::currentMigration();
$fields = clone $row;
$keys = array_keys($this->keySchema);
$values = array();
foreach ($keys as $key) {
$values[] = $row->$key;
}
unset($fields->migrate_map_destid1);
unset($fields->needs_update);
$query = db_merge($this->tableName)->key($keys, $values)->fields((array)$fields);
try {
$status = $query->execute();
if ($status == MergeQuery::STATUS_INSERT) {
$this->numCreated++;
}
else {
$this->numUpdated++;
}
return $values;
}
catch (MigrateException $e) {
$migration->saveMessage($e->getMessage(), $e->getLevel());
Migration::displayMessage($e->getMessage());
}
catch (Exception $e) {
$this->handleException($e);
}
}
public function fields($migration = NULL) {
return array();
}
}

View File

@@ -0,0 +1,267 @@
<?php
/**
* @file
* Support for taxonomy term destinations.
*/
// TODO:
// Make sure this works with updates, explicit destination keys
// taxonomy_term_save() is doing a cache_clear_all and an automatic insertion for parent.
/**
* Destination class implementing migration into terms.
*/
class MigrateDestinationTerm extends MigrateDestinationEntity {
static public function getKeySchema() {
return array(
'tid' => array(
'type' => 'int',
'unsigned' => TRUE,
'description' => 'ID of destination term',
),
);
}
/**
* Return an options array for term destinations.
*
* @param string $language
* Default language for terms created via this destination class.
* @param string $text_format
* Default text format for terms created via this destination class.
*/
static public function options($language, $text_format) {
return compact('language', 'text_format');
}
/**
* Basic initialization
*
* @param array $options
* Options applied to terms.
*/
public function __construct($bundle, array $options = array()) {
parent::__construct('taxonomy_term', $bundle, $options);
}
/**
* Returns a list of fields available to be mapped for this vocabulary (bundle)
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array();
// First the core (taxonomy_term_data table) properties
$fields['tid'] = t('Term: <a href="@doc">Existing term ID</a>',
array('@doc' => 'http://drupal.org/node/1349702#tid'));
$fields['name'] = t('Term: <a href="@doc">Name</a>',
array('@doc' => 'http://drupal.org/node/1349702#name'));
$fields['description'] = t('Term: <a href="@doc">Description</a>',
array('@doc' => 'http://drupal.org/node/1349702#description'));
$fields['parent'] = t('Term: <a href="@doc">Parent (by Drupal term ID)</a>',
array('@doc' => 'http://drupal.org/node/1349702#parent'));
// TODO: Remove parent_name, implement via arguments
$fields['parent_name'] = t('Term: <a href="@doc">Parent (by name)</a>',
array('@doc' => 'http://drupal.org/node/1349702#parent_name'));
$fields['format'] = t('Term: <a href="@doc">Format</a>',
array('@doc' => 'http://drupal.org/node/1349702#format'));
$fields['weight'] = t('Term: <a href="@doc">Weight</a>',
array('@doc' => 'http://drupal.org/node/1349702#weight'));
// Then add in anything provided by handlers
$fields += migrate_handler_invoke_all('entity', 'fields', $this->entityType, $this->bundle, $migration);
$fields += migrate_handler_invoke_all('taxonomy_term', 'fields', $this->entityType, $this->bundle, $migration);
return $fields;
}
/**
* Delete a migrated term
*
* @param $ids
* Array of fields representing the key (in this case, just tid).
*/
public function rollback(array $key) {
$tid = reset($key);
/*
* This load() happens soon delete() anyway. We load here in order to
* avoid notices when term has already been deleted. That is easily possible
* considering how deleting a term parent also deletes children in same call.
*/
migrate_instrument_start('taxonomy_term_load');
if (taxonomy_term_load($tid)) {
migrate_instrument_stop('taxonomy_term_load');
migrate_instrument_start('taxonomy_term_delete');
$this->prepareRollback($tid);
$result = (bool) taxonomy_term_delete($tid);
$this->completeRollback($tid);
migrate_instrument_stop('taxonomy_term_delete');
}
else {
migrate_instrument_stop('taxonomy_term_load');
// If it didn't exist, consider this a success
$result = TRUE;
}
return $result;
}
/**
* Import a single term.
*
* @param $term
* Term object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields (tid only in this case) of the term that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $term, stdClass $row) {
$migration = Migration::currentMigration();
// Updating previously-migrated content?
if (isset($row->migrate_map_destid1)) {
$term->tid = $row->migrate_map_destid1;
if (isset($term->tid)) {
if ($term->tid != $row->migrate_map_destid1) {
throw new MigrateException(t("Incoming tid !tid and map destination nid !destid1 don't match",
array('!tid' => $term->tid, '!destid1' => $row->migrate_map_destid1)));
}
}
else {
$term->tid = $row->migrate_map_destid1;
}
}
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
if (!isset($term->tid)) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination tid provided'));
}
$rawterm = $term;
$this->prepare($term, $row);
$old_term = taxonomy_term_load($term->tid);
if (empty($old_term)) {
throw new MigrateException(t('System-of-record is DESTINATION, but term !tid does not exist',
array('!tid' => $term->tid)));
}
foreach ($rawterm as $field => $value) {
$old_term->$field = $term->$field;
}
$term = $old_term;
}
else {
// Default to bundle if no vocabulary machine name provided
if (!isset($term->vocabulary_machine_name)) {
$term->vocabulary_machine_name = $this->bundle;
}
// vid is required
if (empty($term->vid)) {
static $vocab_map = array();
if (!isset($vocab_map[$term->vocabulary_machine_name])) {
// The keys of the returned array are vids
$vocabs = taxonomy_vocabulary_load_multiple(array(),
array('machine_name' => $term->vocabulary_machine_name));
$vids = array_keys($vocabs);
if (isset($vids[0])) {
$vocab_map[$term->vocabulary_machine_name] = $vids[0];
}
else {
$migration->saveMessage(t('No vocabulary found with machine_name !name',
array('!name' => $term->vocabulary_machine_name)));
return FALSE;
}
}
$term->vid = $vocab_map[$term->vocabulary_machine_name];
}
// Look up parent name if provided
if (isset($term->parent_name) && trim($term->parent_name)) {
// Look for the name in the same vocabulary.
// Note that hierarchies may have multiples of the same name...
$terms = taxonomy_term_load_multiple(array(),
array('name' => trim($term->parent_name), 'vid' => $term->vid));
$tids = array_keys($terms);
$term->parent = array($tids[0]);
unset($term->parent_name);
}
if (empty($term->parent)) {
$term->parent = array(0);
}
if (is_array($term->parent) && isset($term->parent['arguments'])) {
// Unset arguments here to avoid duplicate entries in the
// term_hierarchy table.
unset($term->parent['arguments']);
}
if (!isset($term->format)) {
$term->format = $this->textFormat;
}
$this->prepare($term, $row);
// See if the term, with the same parentage, already exists - if so,
// load it
$candidates = taxonomy_term_load_multiple(array(),
array('name' => trim($term->name), 'vid' => $term->vid));
foreach ($candidates as $candidate) {
$parents = taxonomy_get_parents($candidate->tid);
// We need to set up $parents as a simple array of tids
if (empty($parents)) {
$parents = array(0);
}
else {
// Parents array is tid => term object, make into list of tids
$new_parents = array();
foreach ($parents as $parent) {
$new_parents[] = $parent->tid;
}
$parents = $new_parents;
}
if ($term->parent == $parents) {
// We've found a matching term, we'll use that
$term = $candidate;
break;
}
}
}
// Trying to update an existing term
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
$existing_term = taxonomy_term_load($term->tid);
if ($existing_term) {
// Incoming data overrides existing data, so only copy non-existent fields
foreach ($existing_term as $field => $value) {
if (!isset($term->$field)) {
$term->$field = $existing_term->$field;
}
}
}
}
if (isset($term->tid)) {
$updating = TRUE;
}
else {
$updating = FALSE;
}
migrate_instrument_start('taxonomy_term_save');
$status = taxonomy_term_save($term);
migrate_instrument_stop('taxonomy_term_save');
$this->complete($term, $row);
if (isset($term->tid)) {
if ($updating) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
$return = array($term->tid);
}
else {
$return = FALSE;
}
return $return;
}
}

View File

@@ -0,0 +1,341 @@
<?php
/**
* @file
* Support for user destinations.
*/
// TODO:
// Make sure this works with updates, explicit destination keys
// Speed up password generation a ton: $conf['password_count_log2'] = 1;
/**
* Destination class implementing migration into users.
*/
class MigrateDestinationUser extends MigrateDestinationEntity {
/**
* Indicates whether incoming passwords are md5-encrypted - if so, we will
* rehash them similarly to the D6->D7 upgrade path.
*
* @var boolean
*/
protected $md5Passwords = FALSE;
static public function getKeySchema() {
return array(
'uid' => array(
'type' => 'int',
'unsigned' => TRUE,
'description' => 'ID of destination user',
),
);
}
/**
* Return an options array for user destinations.
*
* @param string $language
* Default language for usrs created via this destination class.
* @param string $text_format
* Default text format for users created via this destination class.
* @param boolean $md5_passwords
* Set TRUE to indicate incoming passwords are md5-encrypted.
*/
static public function options($language, $text_format, $md5_passwords) {
return compact('language', 'text_format', 'md5_passwords');
}
/**
* Basic initialization
*
* @param array $options
* Options applied to comments.
*/
public function __construct(array $options = array()) {
parent::__construct('user', 'user', $options);
if (!empty($options['md5_passwords'])) {
$this->md5Passwords = $options['md5_passwords'];
}
// Reduce hash count so import runs in a reasonable time (use same value as
// the standard Drupal 6=>Drupal 7 upgrade path).
global $conf;
$conf['password_count_log2'] = 11;
}
/**
* Returns a list of fields available to be mapped for users
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array();
// First the core (users table) properties
$fields['uid'] = t('User: <a href="@doc">Existing user ID</a>',
array('@doc' => 'http://drupal.org/node/1349632#uid'));
$fields['mail'] = t('User: <a href="@doc">Email address</a>',
array('@doc' => 'http://drupal.org/node/1349632#mail'));
$fields['name'] = t('User: <a href="@doc">Username</a>',
array('@doc' => 'http://drupal.org/node/1349632#name'));
$fields['pass'] = t('User: <a href="@doc">Password (plain text)</a>',
array('@doc' => 'http://drupal.org/node/1349632#pass'));
$fields['status'] = t('User: <a href="@doc">Status</a>',
array('@doc' => 'http://drupal.org/node/1349632#status'));
$fields['created'] = t('User: <a href="@doc">Registered timestamp</a>',
array('@doc' => 'http://drupal.org/node/1349632#created'));
$fields['access'] = t('User: <a href="@doc">Last access timestamp</a>',
array('@doc' => 'http://drupal.org/node/1349632#access'));
$fields['login'] = t('User: <a href="@doc">Last login timestamp</a>',
array('@doc' => 'http://drupal.org/node/1349632#login'));
$fields['roles'] = t('User: <a href="@doc">Role IDs</a>',
array('@doc' => 'http://drupal.org/node/1349632#roles'));
$fields['role_names'] = t('User: <a href="@doc">Role Names</a>',
array('@doc' => 'http://drupal.org/node/1349632#role_names'));
$fields['picture'] = t('User: <a href="@doc">Picture</a>',
array('@doc' => 'http://drupal.org/node/1349632#picture'));
$fields['signature'] = t('User: <a href="@doc">Signature</a>',
array('@doc' => 'http://drupal.org/node/1349632#signature'));
$fields['signature_format'] = t('User: <a href="@doc">Signature format</a>',
array('@doc' => 'http://drupal.org/node/1349632#signature_format'));
$fields['timezone'] = t('User: <a href="@doc">Timezone</a>',
array('@doc' => 'http://drupal.org/node/1349632#timezone'));
$fields['language'] = t('User: <a href="@doc">Language</a>',
array('@doc' => 'http://drupal.org/node/1349632#language'));
$fields['theme'] = t('User: <a href="@doc">Default theme</a>',
array('@doc' => 'http://drupal.org/node/1349632#theme'));
$fields['init'] = t('User: <a href="@doc">Init</a>',
array('@doc' => 'http://drupal.org/node/1349632#init'));
$fields['is_new'] = t('Option: <a href="@doc">Indicates a new user with the specified uid should be created</a>',
array('@doc' => 'http://drupal.org/node/1349632#is_new'));
// Then add in anything provided by handlers
$fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle, $migration);
$fields += migrate_handler_invoke_all('User', 'fields', $this->entityType, $this->bundle, $migration);
return $fields;
}
/**
* Delete a batch of users at once.
*
* @param $uids
* Array of user IDs to be deleted.
*/
public function bulkRollback(array $uids) {
migrate_instrument_start('user_delete_multiple');
$this->prepareRollback($uids);
user_delete_multiple($uids);
$this->completeRollback($uids);
migrate_instrument_stop('user_delete_multiple');
}
/**
* Import a single user.
*
* @param $account
* Account object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields (uid only in this case) of the user that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $account, stdClass $row) {
$migration = Migration::currentMigration();
// Updating previously-migrated content?
if (isset($row->migrate_map_destid1)) {
// Make sure is_new is off
$account->is_new = FALSE;
if (isset($account->uid)) {
if ($account->uid != $row->migrate_map_destid1) {
throw new MigrateException(t("Incoming uid !uid and map destination uid !destid1 don't match",
array('!uid' => $account->uid, '!destid1' => $row->migrate_map_destid1)));
}
}
else {
$account->uid = $row->migrate_map_destid1;
}
}
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
if (!isset($account->uid)) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination uid provided'));
}
$old_account = user_load($account->uid, TRUE);
if (empty($old_account)) {
throw new MigrateException(t('System-of-record is DESTINATION, but user !uid does not exist',
array('!uid' => $account->uid)));
}
}
else {
$old_account = $account;
}
// Roles must be arrays keyed by the role id, which isn't how the data
// naturally comes in. Fix them up.
// First, if names instead of IDs are presented, translate them
if (!empty($account->role_names)) {
$role_names = is_array($account->role_names) ? $account->role_names : array($account->role_names);
foreach ($role_names as $role_name) {
$role = user_role_load_by_name($role_name);
if ($role) {
$account->roles[] = $role->rid;
}
}
}
if (!empty($account->roles)) {
if (!is_array($account->roles)) {
$account->roles = array($account->roles);
}
$account->roles = drupal_map_assoc($account->roles);
}
if (empty($account->roles) && empty($old_account->roles)) {
$account->roles = array();
}
$this->prepare($account, $row);
if (isset($account->uid) && !(isset($account->is_new) && $account->is_new)) {
$updating = TRUE;
}
else {
$updating = FALSE;
}
// While user_save is happy to see a fid in $account->picture on insert,
// when updating an existing account it wants a file object.
if ($updating && ($fid = $account->picture)) {
$account->picture = file_load($fid);
}
// Normalize MD5 passwords to lowercase, as generated by Drupal 6 and previous
if ($this->md5Passwords) {
$account->pass = drupal_strtolower($account->pass);
}
// If any datetime values were included, ensure that they're in timestamp format.
if (isset($account->created)) {
$account->created = MigrationBase::timestamp($account->created);
}
if (isset($account->access)) {
$account->access = MigrationBase::timestamp($account->access);
}
if (isset($account->login)) {
$account->login = MigrationBase::timestamp($account->login);
}
migrate_instrument_start('user_save');
$newaccount = user_save($old_account, (array)$account);
migrate_instrument_stop('user_save');
if ($newaccount) {
if ($this->md5Passwords && !empty($account->pass)) {
// Ape the Drupal 6 -> Drupal 7 upgrade, which encrypts the MD5 text in the
// modern way, and marks it with a prepended U so it recognizes and fixes it
// up at login time.
$password = 'U' . $newaccount->pass;
db_update('users')
->fields(array('pass' => $password))
->condition('uid', $newaccount->uid)
->execute();
}
if ($updating) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
$this->complete($newaccount, $row);
$return = array($newaccount->uid);
}
else {
$return = FALSE;
}
return $return;
}
}
class MigrateDestinationRole extends MigrateDestinationTable {
public function __construct() {
parent::__construct('role');
}
/**
* Get the key definition for the role table.
*
* @param $dummy
* PHP is picky - it throws E_STRICT notices if we don't have a parameter
* because MigrateDestinationTable has one.
*/
static public function getKeySchema($dummy = NULL) {
return MigrateDestinationTable::getKeySchema('role');
}
/**
* Delete a single row.
*
* @param $id
* Primary key values.
*/
public function rollback(array $id) {
migrate_instrument_start('role rollback');
$rid = reset($id);
user_role_delete((int)$rid);
migrate_instrument_stop('role rollback');
}
/**
* Import a single row.
*
* @param $entity
* Object object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields of the object that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $entity, stdClass $row) {
$migration = Migration::currentMigration();
// Updating previously-migrated content?
if (isset($row->migrate_map_destid1)) {
if (isset($entity->rid)) {
if ($entity->rid != $row->migrate_map_destid1) {
throw new MigrateException(t("Incoming id !id and map destination id !destid don't match",
array('!id' => $entity->rid, '!destid' => $row->migrate_map_destid1)));
}
else {
$entity->rid = $row->migrate_map_destid1;
}
}
}
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
if (!isset($entity->rid)) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination id provided'));
}
$old_entity = user_role_load($entity->rid);
foreach ($entity as $field => $value) {
$old_entity->$field = $entity->$field;
}
$entity = $old_entity;
}
$this->prepare($entity, $row);
user_role_save($entity);
$this->complete($entity, $row);
if (!empty($entity->rid)) {
$id = array($entity->rid);
}
else {
$id = FALSE;
}
return $id;
}
}