first import
This commit is contained in:
295
sites/all/modules/migrate/plugins/destinations/comment.inc
Normal file
295
sites/all/modules/migrate/plugins/destinations/comment.inc
Normal 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;
|
||||
}
|
||||
}
|
||||
174
sites/all/modules/migrate/plugins/destinations/entity.inc
Normal file
174
sites/all/modules/migrate/plugins/destinations/entity.inc
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
671
sites/all/modules/migrate/plugins/destinations/fields.inc
Normal file
671
sites/all/modules/migrate/plugins/destinations/fields.inc
Normal 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'));
|
||||
}
|
||||
}
|
||||
588
sites/all/modules/migrate/plugins/destinations/file.inc
Normal file
588
sites/all/modules/migrate/plugins/destinations/file.inc
Normal 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;
|
||||
}
|
||||
}
|
||||
188
sites/all/modules/migrate/plugins/destinations/menu.inc
Normal file
188
sites/all/modules/migrate/plugins/destinations/menu.inc
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
235
sites/all/modules/migrate/plugins/destinations/menu_links.inc
Normal file
235
sites/all/modules/migrate/plugins/destinations/menu_links.inc
Normal 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();
|
||||
}
|
||||
}
|
||||
296
sites/all/modules/migrate/plugins/destinations/node.inc
Normal file
296
sites/all/modules/migrate/plugins/destinations/node.inc
Normal 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;
|
||||
}
|
||||
}
|
||||
29
sites/all/modules/migrate/plugins/destinations/path.inc
Normal file
29
sites/all/modules/migrate/plugins/destinations/path.inc
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
sites/all/modules/migrate/plugins/destinations/poll.inc
Normal file
121
sites/all/modules/migrate/plugins/destinations/poll.inc
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
210
sites/all/modules/migrate/plugins/destinations/table.inc
Normal file
210
sites/all/modules/migrate/plugins/destinations/table.inc
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
267
sites/all/modules/migrate/plugins/destinations/term.inc
Normal file
267
sites/all/modules/migrate/plugins/destinations/term.inc
Normal 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;
|
||||
}
|
||||
}
|
||||
341
sites/all/modules/migrate/plugins/destinations/user.inc
Normal file
341
sites/all/modules/migrate/plugins/destinations/user.inc
Normal 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;
|
||||
}
|
||||
}
|
||||
208
sites/all/modules/migrate/plugins/sources/csv.inc
Normal file
208
sites/all/modules/migrate/plugins/sources/csv.inc
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Define a MigrateSource for importing from comma separated values files.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource, to handle imports from CSV files.
|
||||
*
|
||||
* If the CSV file contains non-ASCII characters, make sure it includes a
|
||||
* UTF BOM (Byte Order Marker) so they are interpreted correctly.
|
||||
*/
|
||||
class MigrateSourceCSV extends MigrateSource {
|
||||
/**
|
||||
* List of available source fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* Parameters for the fgetcsv() call.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fgetcsv = array();
|
||||
|
||||
/**
|
||||
* File handle for the CSV file being iterated.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $csvHandle = NULL;
|
||||
|
||||
/**
|
||||
* The number of rows in the CSV file before the data starts.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $headerRows = 0;
|
||||
|
||||
/**
|
||||
* Simple initialization.
|
||||
*
|
||||
* @param string $path
|
||||
* The path to the source file
|
||||
* @param array $csvcolumns
|
||||
* Keys are integers. values are array(field name, description).
|
||||
* @param array $options
|
||||
* Options applied to this source.
|
||||
* @param array $fields
|
||||
* Optional - keys are field names, values are descriptions. Use to override
|
||||
* the default descriptions, or to add additional source fields which the
|
||||
* migration will add via other means (e.g., prepareRow()).
|
||||
*/
|
||||
public function __construct($path, array $csvcolumns = array(), array $options = array(), array $fields = array()) {
|
||||
parent::__construct($options);
|
||||
$this->file = $path;
|
||||
if (!empty($options['header_rows'])) {
|
||||
$this->headerRows = $options['header_rows'];
|
||||
}
|
||||
else {
|
||||
$this->headerRows = 0;
|
||||
}
|
||||
$this->options = $options;
|
||||
$this->fields = $fields;
|
||||
// fgetcsv specific options
|
||||
foreach (array('length' => NULL, 'delimiter' => ',', 'enclosure' => '"', 'escape' => '\\') as $key => $default) {
|
||||
$this->fgetcsv[$key] = isset($options[$key]) ? $options[$key] : $default;
|
||||
}
|
||||
// One can either pass in an explicit list of column names to use, or if we have
|
||||
// a header row we can use the names from that
|
||||
if ($this->headerRows && empty($csvcolumns)) {
|
||||
$this->csvcolumns = array();
|
||||
$this->csvHandle = fopen($this->file, 'r');
|
||||
// Skip all but the last header
|
||||
for ($i = 0; $i < $this->headerRows - 1; $i++) {
|
||||
$this->getNextLine();
|
||||
}
|
||||
|
||||
$row = $this->getNextLine();
|
||||
foreach ($row as $header) {
|
||||
$header = trim($header);
|
||||
$this->csvcolumns[] = array($header, $header);
|
||||
}
|
||||
fclose($this->csvHandle);
|
||||
unset($this->csvHandle);
|
||||
}
|
||||
else {
|
||||
$this->csvcolumns = $csvcolumns;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representing the source query.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of fields available to be mapped from the source query.
|
||||
*
|
||||
* @return array
|
||||
* Keys: machine names of the fields (to be passed to addFieldMapping)
|
||||
* Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = array();
|
||||
foreach ($this->csvcolumns as $values) {
|
||||
$fields[$values[0]] = $values[1];
|
||||
}
|
||||
|
||||
// Any caller-specified fields with the same names as extracted fields will
|
||||
// override them; any others will be added
|
||||
if ($this->fields) {
|
||||
$fields = $this->fields + $fields;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of all available source records.
|
||||
*/
|
||||
public function computeCount() {
|
||||
// If the data may have embedded newlines, the file line count won't reflect
|
||||
// the number of CSV records (one record will span multiple lines). We need
|
||||
// to scan with fgetcsv to get the true count.
|
||||
if (!empty($this->options['embedded_newlines'])) {
|
||||
$result = fopen($this->file, 'r');
|
||||
// Skip all but the last header
|
||||
for ($i = 0; $i < $this->headerRows; $i++) {
|
||||
fgets($result);
|
||||
}
|
||||
$count = 0;
|
||||
while ($this->getNextLine()) {
|
||||
$count++;
|
||||
}
|
||||
fclose($result);
|
||||
}
|
||||
else {
|
||||
// TODO. If this takes too much time/memory, use exec('wc -l')
|
||||
$count = count(file($this->file));
|
||||
$count -= $this->headerRows;
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::performRewind().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function performRewind() {
|
||||
// Close any previously-opened handle
|
||||
if (!is_null($this->csvHandle)) {
|
||||
fclose($this->csvHandle);
|
||||
}
|
||||
// Load up the first row, skipping the header(s) if necessary
|
||||
$this->csvHandle = fopen($this->file, 'r');
|
||||
for ($i = 0; $i < $this->headerRows; $i++) {
|
||||
$this->getNextLine();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::getNextRow().
|
||||
* Return the next line of the source CSV file as an object.
|
||||
*
|
||||
* @return null|object
|
||||
*/
|
||||
public function getNextRow() {
|
||||
$row = $this->getNextLine();
|
||||
if ($row) {
|
||||
// Set meaningful keys for the columns mentioned in $this->csvcolumns().
|
||||
foreach ($this->csvcolumns as $int => $values) {
|
||||
list($key, $description) = $values;
|
||||
// Copy value to more descriptive string based key and then unset original.
|
||||
$row[$key] = isset($row[$int]) ? $row[$int] : NULL;
|
||||
unset($row[$int]);
|
||||
}
|
||||
return (object)$row;
|
||||
}
|
||||
else {
|
||||
fclose($this->csvHandle);
|
||||
$this->csvHandle = NULL;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getNextLine() {
|
||||
// escape parameter was added in PHP 5.3.
|
||||
if (version_compare(phpversion(), '5.3', '<')) {
|
||||
$row = fgetcsv($this->csvHandle, $this->fgetcsv['length'],
|
||||
$this->fgetcsv['delimiter'], $this->fgetcsv['enclosure']);
|
||||
}
|
||||
else {
|
||||
$row = fgetcsv($this->csvHandle, $this->fgetcsv['length'],
|
||||
$this->fgetcsv['delimiter'], $this->fgetcsv['enclosure'],
|
||||
$this->fgetcsv['escape']);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
175
sites/all/modules/migrate/plugins/sources/files.inc
Normal file
175
sites/all/modules/migrate/plugins/sources/files.inc
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Support for migration from files sources.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of MigrateList, for retrieving a list of IDs to be migrated
|
||||
* from a directory listing. Each item is a file, it's ID is the path.
|
||||
*/
|
||||
class MigrateListFiles extends MigrateList {
|
||||
|
||||
protected $listDirs;
|
||||
protected $baseDir;
|
||||
protected $fileMask;
|
||||
protected $directoryOptions;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param $list_dirs
|
||||
* Array of directory paths that will be scanned for files. No trailing
|
||||
* slash. For example:
|
||||
* array(
|
||||
* '/var/html_source/en/news',
|
||||
* '/var/html_source/fr/news',
|
||||
* '/var/html_source/zh/news',
|
||||
* );
|
||||
* @param $base_dir
|
||||
* The base dir is the part of the path that will be excluded when making
|
||||
* an ID for each file. To continue the example from above, you want base_dir
|
||||
* to be = '/var/html_source', so that the files will have IDs in the format
|
||||
* '/en/news/news_2011_03_4.html'.
|
||||
* @param $file_mask
|
||||
* Passed on and used to filter for certain types of files. Use a regular
|
||||
* expression, for example '/(.*\.htm$|.*\.html$)/i' to match all .htm and
|
||||
* .html files, case insensitive.
|
||||
* @param $options
|
||||
* Options that will be passed on to file_scan_directory(). See docs of that
|
||||
* core Drupal function for more information.
|
||||
*/
|
||||
public function __construct($list_dirs, $base_dir, $file_mask = NULL, $options = array()) {
|
||||
parent::__construct();
|
||||
$this->listDirs = $list_dirs;
|
||||
$this->baseDir = $base_dir;
|
||||
$this->fileMask = $file_mask;
|
||||
$this->directoryOptions = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our public face is the directories we're getting items from.
|
||||
*/
|
||||
public function __toString() {
|
||||
if (is_array($this->listDirs)) {
|
||||
return implode(',', $this->listDirs);
|
||||
}
|
||||
else {
|
||||
return $this->listDirs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of files based on parameters passed for the migration.
|
||||
*/
|
||||
public function getIdList() {
|
||||
$files = array();
|
||||
foreach ($this->listDirs as $dir) {
|
||||
migrate_instrument_start("Retrieve $dir");
|
||||
$files = array_merge(file_scan_directory($dir, $this->fileMask, $this->directoryOptions), $files);
|
||||
migrate_instrument_stop("Retrieve $dir");
|
||||
}
|
||||
|
||||
if (isset($files)) {
|
||||
return $this->getIDsFromFiles($files);
|
||||
}
|
||||
Migration::displayMessage(t('Loading of !listuri failed:', array('!listuri' => $this->listUri)));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array generated from file_scan_directory(), parse out the IDs for
|
||||
* processing and return them as an array.
|
||||
*/
|
||||
protected function getIDsFromFiles(array $files) {
|
||||
$ids = array();
|
||||
foreach ($files as $file) {
|
||||
$ids[] = str_replace($this->baseDir, '', (string) $file->uri);
|
||||
}
|
||||
return array_unique($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of all available IDs from the source listing.
|
||||
*/
|
||||
public function computeCount() {
|
||||
$count = 0;
|
||||
$files = $this->getIdList();
|
||||
if ($files) {
|
||||
$count = count($files);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateItem, for retrieving a file from the file system
|
||||
* based on source directory and an ID provided by a MigrateList class.
|
||||
*/
|
||||
class MigrateItemFile extends MigrateItem {
|
||||
|
||||
protected $baseDir;
|
||||
protected $getContents;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param $base_dir
|
||||
* The base directory from which all file paths are calculated.
|
||||
* @param $get_contents
|
||||
* TRUE if we should try load the contents of each file (in the case
|
||||
* of a text file), or FALSE if we just want to confirm it exists (binary).
|
||||
*/
|
||||
public function __construct($base_dir, $get_contents = TRUE) {
|
||||
parent::__construct();
|
||||
$this->baseDir = $base_dir;
|
||||
$this->getContents = $get_contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object representing a file.
|
||||
*
|
||||
* @param $id
|
||||
* The file id, which is the file URI.
|
||||
*
|
||||
* @return object
|
||||
* The item object for migration.
|
||||
*/
|
||||
public function getItem($id) {
|
||||
$item_uri = $this->baseDir . $id;
|
||||
// Get the file data at the specified URI
|
||||
$data = $this->loadFile($item_uri);
|
||||
if (is_string($data)) {
|
||||
$return = new stdClass;
|
||||
$return->filedata = $data;
|
||||
return $return;
|
||||
}
|
||||
elseif ($data === TRUE) {
|
||||
$return = new stdClass;
|
||||
return $return;
|
||||
}
|
||||
else {
|
||||
$migration = Migration::currentMigration();
|
||||
$message = t('Loading of !objecturi failed:', array('!objecturi' => $item_uri));
|
||||
$migration->getMap()->saveMessage(
|
||||
array($id), $message, MigrationBase::MESSAGE_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default file loader.
|
||||
*/
|
||||
protected function loadFile($item_uri) {
|
||||
// Only try load the contents if we have this flag set.
|
||||
if ($this->getContents) {
|
||||
$data = file_get_contents($item_uri);
|
||||
}
|
||||
else {
|
||||
$data = file_exists($item_uri);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
172
sites/all/modules/migrate/plugins/sources/json.inc
Normal file
172
sites/all/modules/migrate/plugins/sources/json.inc
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Support for migration from JSON sources.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of MigrateList, for retrieving a list of IDs to be migrated
|
||||
* from a JSON object.
|
||||
*/
|
||||
class MigrateListJSON extends MigrateList {
|
||||
/**
|
||||
* A URL pointing to an JSON object containing a list of IDs to be processed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $listUrl;
|
||||
|
||||
protected $httpOptions;
|
||||
|
||||
public function __construct($list_url, $http_options = array()) {
|
||||
parent::__construct();
|
||||
$this->listUrl = $list_url;
|
||||
$this->httpOptions = $http_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our public face is the URL we're getting items from
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->listUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the JSON at the given URL, and return an array of the IDs found within it.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIdList() {
|
||||
migrate_instrument_start("Retrieve $this->listUrl");
|
||||
if (empty($this->httpOptions)) {
|
||||
$json = file_get_contents($this->listUrl);
|
||||
}
|
||||
else {
|
||||
$response = drupal_http_request($this->listUrl, $this->httpOptions);
|
||||
$json = $response->data;
|
||||
}
|
||||
migrate_instrument_stop("Retrieve $this->listUrl");
|
||||
if ($json) {
|
||||
$data = drupal_json_decode($json);
|
||||
if ($data) {
|
||||
return $this->getIDsFromJSON($data);
|
||||
}
|
||||
}
|
||||
Migration::displayMessage(t('Loading of !listurl failed:',
|
||||
array('!listurl' => $this->listUrl)));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array generated from JSON, parse out the IDs for processing
|
||||
* and return them as an array. The default implementation assumes the IDs are
|
||||
* simply the values of the top-level elements - in most cases, you will need
|
||||
* to override this to reflect your particular JSON structure.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getIDsFromJSON(array $data) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of all available IDs from the source listing. The default
|
||||
* implementation assumes the count of top-level elements reflects the number
|
||||
* of IDs available - in many cases, you will need to override this to reflect
|
||||
* your particular JSON structure.
|
||||
*/
|
||||
public function computeCount() {
|
||||
$count = 0;
|
||||
if (empty($this->httpOptions)) {
|
||||
$json = file_get_contents($this->listUrl);
|
||||
}
|
||||
else {
|
||||
$response = drupal_http_request($this->listUrl, $this->httpOptions);
|
||||
$json = $response->data;
|
||||
}
|
||||
if ($json) {
|
||||
$data = drupal_json_decode($json);
|
||||
if ($data) {
|
||||
$count = count($data);
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateItem, for retrieving a parsed JSON object given
|
||||
* an ID provided by a MigrateList class.
|
||||
*/
|
||||
class MigrateItemJSON extends MigrateItem {
|
||||
/**
|
||||
* A URL pointing to a JSON object containing the data for one item to be
|
||||
* migrated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $itemUrl;
|
||||
|
||||
protected $httpOptions;
|
||||
|
||||
public function __construct($item_url, $http_options) {
|
||||
parent::__construct();
|
||||
$this->itemUrl = $item_url;
|
||||
$this->httpOptions = $http_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementors are expected to return an object representing a source item.
|
||||
*
|
||||
* @param mixed $id
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function getItem($id) {
|
||||
$item_url = $this->constructItemUrl($id);
|
||||
// Get the JSON object at the specified URL
|
||||
$json = $this->loadJSONUrl($item_url);
|
||||
if ($json) {
|
||||
return $json;
|
||||
}
|
||||
else {
|
||||
$migration = Migration::currentMigration();
|
||||
$message = t('Loading of !objecturl failed:', array('!objecturl' => $item_url));
|
||||
$migration->getMap()->saveMessage(
|
||||
array($id), $message, MigrationBase::MESSAGE_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation simply replaces the :id token in the URL with
|
||||
* the ID obtained from MigrateListJSON. Override if the item URL is not
|
||||
* so easily expressed from the ID.
|
||||
*
|
||||
* @param mixed $id
|
||||
*/
|
||||
protected function constructItemUrl($id) {
|
||||
return str_replace(':id', $id, $this->itemUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default JSON loader - just pull and decode. This can be overridden for
|
||||
* preprocessing of JSON (removal of unwanted elements, caching of JSON if the
|
||||
* source service is slow, etc.)
|
||||
*/
|
||||
protected function loadJSONUrl($item_url) {
|
||||
if (empty($this->httpOptions)) {
|
||||
$json = file_get_contents($item_url);
|
||||
}
|
||||
else {
|
||||
$response = drupal_http_request($item_url, $this->httpOptions);
|
||||
$json = $response->data;
|
||||
}
|
||||
return json_decode($json);
|
||||
}
|
||||
}
|
||||
195
sites/all/modules/migrate/plugins/sources/list.inc
Normal file
195
sites/all/modules/migrate/plugins/sources/list.inc
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Support for migration from sources with distinct means of listing items to
|
||||
* import and obtaining the items themselves.
|
||||
*
|
||||
* TODO: multiple-field source keys
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Extend the MigrateList class to provide a means to obtain a list of IDs to
|
||||
* be migrated from a given source (e.g., MigrateListXML extends MigrateList to
|
||||
* obtain a list of IDs from an XML document).
|
||||
*/
|
||||
abstract class MigrateList {
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Implementors are expected to return a string representing where the listing
|
||||
* is obtained from (a URL, file directory, etc.)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function __toString();
|
||||
|
||||
/**
|
||||
* Implementors are expected to return an array of unique IDs, suitable for
|
||||
* passing to the MigrateItem class to retrieve the data for a single item.
|
||||
*
|
||||
* @return Mixed, iterator or array
|
||||
*/
|
||||
abstract public function getIdList();
|
||||
|
||||
/**
|
||||
* Implementors are expected to return a count of IDs available to be migrated.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function computeCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the MigrateItem class to provide a means to obtain the data for a
|
||||
* given migratable item given its ID as provided by the MigrateList class.
|
||||
*/
|
||||
abstract class MigrateItem {
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Implementors are expected to return an object representing a source item.
|
||||
*
|
||||
* @param mixed $id
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
abstract public function getItem($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource, providing the semantics of iterating over
|
||||
* IDs provided by a MigrateList and retrieving data from a MigrateItem.
|
||||
*/
|
||||
class MigrateSourceList extends MigrateSource {
|
||||
/**
|
||||
* MigrateList object used to obtain ID lists.
|
||||
*
|
||||
* @var MigrateList
|
||||
*/
|
||||
protected $listClass;
|
||||
|
||||
/**
|
||||
* MigrateItem object used to obtain the source object for a given ID.
|
||||
*
|
||||
* @var MigrateItem
|
||||
*/
|
||||
protected $itemClass;
|
||||
|
||||
/**
|
||||
* Iterator of IDs from the listing class.
|
||||
*
|
||||
* @var Iterator
|
||||
*/
|
||||
protected $idIterator;
|
||||
|
||||
/**
|
||||
* List of available source fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* Simple initialization.
|
||||
*/
|
||||
public function __construct(MigrateList $list_class, MigrateItem $item_class, $fields = array(),
|
||||
$options = array()) {
|
||||
parent::__construct($options);
|
||||
$this->listClass = $list_class;
|
||||
$this->itemClass = $item_class;
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representing the source.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return (string) $this->listClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of fields available to be mapped from the source query.
|
||||
* Since we can't reliably figure out what "fields" are in the source,
|
||||
* it's up to the implementing Migration constructor to fill them in.
|
||||
*
|
||||
* @return array
|
||||
* Keys: machine names of the fields (to be passed to addFieldMapping)
|
||||
* Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields() {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's the list class that knows how many records are available, so ask it.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function computeCount() {
|
||||
// @API: Support old count method for now.
|
||||
if (method_exists($this->listClass, 'computeCount')) {
|
||||
return $this->listClass->computeCount();
|
||||
}
|
||||
else {
|
||||
return $this->listClass->count();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::performRewind().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function performRewind() {
|
||||
// If there isn't a specific ID list passed in, get it from the list class.
|
||||
if ($this->idList) {
|
||||
$this->idsToProcess = $this->idList;
|
||||
}
|
||||
else {
|
||||
$this->idsToProcess = $this->listClass->getIdList();
|
||||
}
|
||||
$this->idIterator = ($this->idsToProcess instanceof Iterator) ?
|
||||
$this->idsToProcess : new ArrayIterator($this->idsToProcess);
|
||||
$this->idIterator->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::getNextRow().
|
||||
*
|
||||
* @return null|stdClass
|
||||
*/
|
||||
public function getNextRow() {
|
||||
$row = NULL;
|
||||
while ($this->idIterator->valid()) {
|
||||
$ids = $this->idIterator->current();
|
||||
$this->idIterator->next();
|
||||
|
||||
// Skip empty IDs
|
||||
if (empty($ids)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Got a good ID, get the data and get out.
|
||||
$row = $this->itemClass->getItem($ids);
|
||||
if ($row) {
|
||||
// No matter what $ids is, be it a string, integer, object, or array, we
|
||||
// cast it to an array so that it can be properly mapped to the source
|
||||
// keys as specified by the map. This is done after getItem is called so
|
||||
// that the ItemClass doesn't have to care about this requirement.
|
||||
$ids = (array) $ids;
|
||||
foreach (array_keys($this->activeMap->getSourceKey()) as $key_name) {
|
||||
// Grab the first id and advance the array cursor. Then save the ID
|
||||
// using the map source key - it will be used for mapping.
|
||||
list(, $id) = each($ids);
|
||||
$row->$key_name = $id;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
206
sites/all/modules/migrate/plugins/sources/mssql.inc
Normal file
206
sites/all/modules/migrate/plugins/sources/mssql.inc
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Define a MigrateSource for importing from Microsoft SQL Server databases.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource, to handle imports from remote MS SQL Server db servers.
|
||||
*/
|
||||
class MigrateSourceMSSQL extends MigrateSource {
|
||||
/**
|
||||
* Array containing information for connecting to SQL Server:
|
||||
* servername - Hostname of the SQL Server
|
||||
* username - Username to connect as
|
||||
* password - Password for logging in
|
||||
* database (optional) - Database to select after connecting
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* The active MS SQL Server connection for this source.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The SQL query from which to obtain data. Is a string.
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* The result object from executing the query - traversed to process the
|
||||
* incoming data.
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* By default, mssql_query fetches all results - severe memory problems with
|
||||
* big tables. So, we will fetch a batch at a time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $batchSize;
|
||||
|
||||
/**
|
||||
* Return an options array for MS SQL sources.
|
||||
*
|
||||
* @param int $batch_size
|
||||
* Number of rows to pull at once (defaults to 500).
|
||||
* @param boolean $cache_counts
|
||||
* Indicates whether to cache counts of source records.
|
||||
*/
|
||||
static public function options($batch_size, $cache_counts) {
|
||||
return compact('batch_size', 'cache_counts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple initialization.
|
||||
*/
|
||||
public function __construct(array $configuration, $query, $count_query,
|
||||
array $fields, array $options = array()) {
|
||||
parent::__construct($options);
|
||||
$this->query = $query;
|
||||
$this->countQuery = $count_query;
|
||||
$this->configuration = $configuration;
|
||||
$this->fields = $fields;
|
||||
$this->batchSize = isset($options['batch_size']) ? $options['batch_size'] : 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representing the source query.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect lazily to the DB server.
|
||||
*/
|
||||
protected function connect() {
|
||||
if (!isset($this->connection)) {
|
||||
if (!extension_loaded('mssql')) {
|
||||
throw new Exception(t('You must configure the mssql extension in PHP.'));
|
||||
}
|
||||
|
||||
if (isset($this->configuration['port'])) {
|
||||
$host = $this->configuration['servername'] . ':' . $this->configuration['port'];
|
||||
}
|
||||
else {
|
||||
$host = $this->configuration['servername'];
|
||||
}
|
||||
$this->connection = mssql_connect(
|
||||
$host,
|
||||
$this->configuration['username'],
|
||||
$this->configuration['password'],
|
||||
TRUE);
|
||||
if (isset($this->configuration['database'])) {
|
||||
return mssql_select_db($this->configuration['database'], $this->connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of fields available to be mapped from the source query.
|
||||
*
|
||||
* @return array
|
||||
* Keys: machine names of the fields (to be passed to addFieldMapping)
|
||||
* Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields() {
|
||||
// The fields are passed to the constructor for this plugin.
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of all available source records.
|
||||
*/
|
||||
public function computeCount() {
|
||||
migrate_instrument_start('MigrateSourceMSSQL count');
|
||||
if ($this->connect()) {
|
||||
$result = mssql_query($this->countQuery);
|
||||
$count = reset(mssql_fetch_object($result));
|
||||
}
|
||||
else {
|
||||
// Do something else?
|
||||
$count = FALSE;
|
||||
}
|
||||
migrate_instrument_stop('MigrateSourceMSSQL count');
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::performRewind().
|
||||
*/
|
||||
public function performRewind() {
|
||||
/*
|
||||
* Replace :criteria placeholder with idlist or highwater clauses. We
|
||||
* considered supporting both but it is not worth the complexity. Run twice
|
||||
* instead.
|
||||
*/
|
||||
if (!empty($this->idList)) {
|
||||
$keys = array();
|
||||
foreach ($this->activeMap->getSourceKey() as $field_name => $field_schema) {
|
||||
// Allow caller to provide an alias to table containing the primary key.
|
||||
if (!empty($field_schema['alias'])) {
|
||||
$field_name = $field_schema['alias'] . '.' . $field_name;
|
||||
}
|
||||
$keys[] = $field_name;
|
||||
}
|
||||
|
||||
// TODO: Sanitize. not critical as this is admin supplied data in drush.
|
||||
$this->query = str_replace(':criteria',
|
||||
$keys[0] . ' IN (' . implode(',', $this->idList) . ')', $this->query);
|
||||
}
|
||||
else {
|
||||
if (isset($this->highwaterField['name']) && $highwater = $this->activeMigration->getHighwater()) {
|
||||
if (empty($this->highwaterField['alias'])) {
|
||||
$highwater_name = $this->highwaterField['name'];
|
||||
}
|
||||
else {
|
||||
$highwater_name = $this->highwaterField['alias'] . '.' . $this->highwaterField['name'];
|
||||
}
|
||||
$this->query = str_replace(':criteria', "$highwater_name > '$highwater'", $this->query);
|
||||
}
|
||||
else {
|
||||
// No idlist or highwater. Replace :criteria placeholder with harmless WHERE
|
||||
// clause instead of empty since we don't know if an AND follows.
|
||||
$this->query = str_replace(':criteria', '1=1', $this->query);
|
||||
}
|
||||
}
|
||||
|
||||
migrate_instrument_start('mssql_query');
|
||||
$this->connect();
|
||||
$this->result = mssql_query($this->query, $this->connection, $this->batchSize);
|
||||
migrate_instrument_stop('mssql_query');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::getNextRow().
|
||||
*
|
||||
* Returns the next row of the result set as an object, dealing with the
|
||||
* difference between the end of the batch and the end of all data.
|
||||
*/
|
||||
public function getNextRow() {
|
||||
$row = mssql_fetch_object($this->result);
|
||||
|
||||
// Might be totally out of data, or just out of this batch - request another
|
||||
// batch and see
|
||||
if (!is_object($row)) {
|
||||
mssql_fetch_batch($this->result);
|
||||
$row = mssql_fetch_object($this->result);
|
||||
}
|
||||
if (is_object($row)) {
|
||||
return $row;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
186
sites/all/modules/migrate/plugins/sources/multiitems.inc
Normal file
186
sites/all/modules/migrate/plugins/sources/multiitems.inc
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Support for migration from sources where data spans multiple lines
|
||||
* (ex. xml, json) and IDs for the items are part of each item and multiple
|
||||
* items reside in a single file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extend the MigrateItems class to provide a means to obtain a list of IDs to
|
||||
* be migrated from a given source (e.g., MigrateItemsXML extends MigrateItem to
|
||||
* obtain a list of IDs from an XML document). This class also provides a means
|
||||
* to obtain the data for a given migratable item given its ID.
|
||||
*/
|
||||
abstract class MigrateItems {
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Implementors are expected to return a string representing where the listing
|
||||
* is obtained from (a URL, file directory, etc.)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function __toString();
|
||||
|
||||
/**
|
||||
* Implementors are expected to return an array of unique IDs, suitable for
|
||||
* passing to the MigrateItem class to retrieve the data for a single item.
|
||||
*
|
||||
* @return Mixed, iterator or array
|
||||
*/
|
||||
abstract public function getIdList();
|
||||
|
||||
/**
|
||||
* Implementors are expected to return a count of IDs available to be migrated.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function computeCount();
|
||||
|
||||
/**
|
||||
* Implementors are expected to return an object representing a source item.
|
||||
*
|
||||
* @param mixed $id
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
abstract public function getItem($id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of MigrateItems, for providing a list of IDs and for
|
||||
* retrieving a parsed XML document given an ID from this list.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource, providing the semantics of iterating over
|
||||
* IDs provided by a MigrateItems and retrieving data from a MigrateItems.
|
||||
*/
|
||||
class MigrateSourceMultiItems extends MigrateSource {
|
||||
/**
|
||||
* MigrateItems object used to obtain the list of IDs and source for
|
||||
* all objects.
|
||||
*
|
||||
* @var MigrateItems
|
||||
*/
|
||||
protected $itemsClass;
|
||||
|
||||
/**
|
||||
* List of available source fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* Iterator of IDs from the listing class.
|
||||
*
|
||||
* @var Iterator
|
||||
*/
|
||||
protected $idIterator;
|
||||
|
||||
/**
|
||||
* List of item IDs to iterate.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $idsToProcess = array();
|
||||
|
||||
/**
|
||||
* Simple initialization.
|
||||
*/
|
||||
public function __construct(MigrateItems $items_class, $fields = array(), $options = array()) {
|
||||
parent::__construct($options);
|
||||
|
||||
$this->itemsClass = $items_class;
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representing the source.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return (string) $this->itemsClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of fields available to be mapped from the source query.
|
||||
* Since we can't reliably figure out what "fields" are in the source,
|
||||
* it's up to the implementing Migration constructor to fill them in.
|
||||
*
|
||||
* @return array
|
||||
* Keys: machine names of the fields (to be passed to addFieldMapping)
|
||||
* Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields() {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's the list class that knows how many records are available, so ask it.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function computeCount() {
|
||||
// @API: Support old count method for now.
|
||||
if (method_exists($this->itemsClass, 'computeCount')) {
|
||||
return $this->itemsClass->computeCount();
|
||||
}
|
||||
else {
|
||||
return $this->itemsClass->count();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::performRewind().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function performRewind() {
|
||||
// If there isn't a specific ID list passed in, get it from the list class.
|
||||
if ($this->idList) {
|
||||
$this->idsToProcess = $this->idList;
|
||||
}
|
||||
else {
|
||||
$this->idsToProcess = $this->itemsClass->getIdList();
|
||||
}
|
||||
$this->idIterator = ($this->idsToProcess instanceof Iterator) ?
|
||||
$this->idsToProcess : new ArrayIterator($this->idsToProcess);
|
||||
$this->idIterator->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::getNextRow().
|
||||
*
|
||||
* @return null|stdClass
|
||||
*/
|
||||
public function getNextRow() {
|
||||
$row = NULL;
|
||||
while ($this->idIterator->valid()) {
|
||||
$id = $this->idIterator->current();
|
||||
$this->idIterator->next();
|
||||
|
||||
// Skip empty IDs
|
||||
if (empty($id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Got a good ID, get the data and get out.
|
||||
$row = $this->itemsClass->getItem($id);
|
||||
if ($row) {
|
||||
// Save the ID using the map source key - it will be used for mapping
|
||||
$sourceKey = $this->activeMap->getSourceKey();
|
||||
$key_name = key($sourceKey);
|
||||
$row->$key_name = $id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
221
sites/all/modules/migrate/plugins/sources/oracle.inc
Normal file
221
sites/all/modules/migrate/plugins/sources/oracle.inc
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Define a MigrateSource class for importing from Oracle databases.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource, to handle imports from remote Oracle servers.
|
||||
*/
|
||||
class MigrateSourceOracle extends MigrateSource {
|
||||
/**
|
||||
* Array containing information for connecting to Oracle:
|
||||
* username - Username to connect as
|
||||
* password - Password for logging in
|
||||
* connection_string - See http://us.php.net/manual/en/function.oci-connect.php.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* The active Oracle connection for this source.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $connection;
|
||||
public function getConnection() {
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SQL query from which to obtain data. Is a string.
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* The result object from executing the query - traversed to process the
|
||||
* incoming data.
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* Character set to use in retrieving data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $characterSet;
|
||||
|
||||
/**
|
||||
* Return an options array for Oracle sources.
|
||||
*
|
||||
* @param string $character_set
|
||||
* Character set to use in retrieving data. Defaults to 'UTF8'.
|
||||
* @param boolean $cache_counts
|
||||
* Indicates whether to cache counts of source records.
|
||||
*/
|
||||
static public function options($character_set = 'UTF8', $cache_counts = FALSE) {
|
||||
return compact('character_set', 'cache_counts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple initialization.
|
||||
*/
|
||||
public function __construct(array $configuration, $query, $count_query,
|
||||
array $fields, array $options = array()) {
|
||||
parent::__construct($options);
|
||||
$this->query = $query;
|
||||
$this->countQuery = $count_query;
|
||||
$this->configuration = $configuration;
|
||||
$this->fields = $fields;
|
||||
if (empty($options['character_set'])) {
|
||||
$this->characterSet = 'UTF8';
|
||||
}
|
||||
else {
|
||||
$this->characterSet = $options['character_set'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representing the source query.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect lazily to the DB server.
|
||||
*/
|
||||
protected function connect() {
|
||||
if (!isset($this->connection)) {
|
||||
if (!extension_loaded('oci8')) {
|
||||
throw new Exception(t('You must configure the oci8 extension in PHP.'));
|
||||
}
|
||||
$this->connection = oci_connect($this->configuration['username'],
|
||||
$this->configuration['password'], $this->configuration['connection_string'],
|
||||
$this->characterSet);
|
||||
}
|
||||
if ($this->connection) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
$e = oci_error();
|
||||
throw new Exception($e['message']);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of fields available to be mapped from the source query.
|
||||
*
|
||||
* @return array
|
||||
* Keys: machine names of the fields (to be passed to addFieldMapping)
|
||||
* Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields() {
|
||||
// The fields are passed to the constructor for this plugin.
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of all available source records.
|
||||
*/
|
||||
public function computeCount() {
|
||||
migrate_instrument_start('MigrateSourceOracle count');
|
||||
if ($this->connect()) {
|
||||
$statement = oci_parse($this->connection, $this->countQuery);
|
||||
if (!$statement) {
|
||||
$e = oci_error($this->connection);
|
||||
throw new Exception($e['message'] . "\n" . $e['sqltext']);
|
||||
}
|
||||
$result = oci_execute($statement);
|
||||
if (!$result) {
|
||||
$e = oci_error($statement);
|
||||
throw new Exception($e['message'] . "\n" . $e['sqltext']);
|
||||
}
|
||||
$count_array = oci_fetch_array($statement);
|
||||
$count = reset($count_array);
|
||||
}
|
||||
else {
|
||||
// Do something else?
|
||||
$count = FALSE;
|
||||
}
|
||||
migrate_instrument_stop('MigrateSourceOracle count');
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::performRewind().
|
||||
*/
|
||||
public function performRewind() {
|
||||
$keys = array();
|
||||
foreach ($this->activeMap->getSourceKey() as $field_name => $field_schema) {
|
||||
// Allow caller to provide an alias to table containing the primary key.
|
||||
if (!empty($field_schema['alias'])) {
|
||||
$field_name = $field_schema['alias'] . '.' . $field_name;
|
||||
}
|
||||
$keys[] = $field_name;
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace :criteria placeholder with idlist or highwater clauses. We
|
||||
* considered supporting both but it is not worth the complexity. Run twice
|
||||
* instead.
|
||||
*/
|
||||
if (!empty($this->idList)) {
|
||||
// TODO: Sanitize. not critical as this is admin supplied data in drush.
|
||||
$this->query = str_replace(':criteria',
|
||||
$keys[0] . ' IN (' . implode(',', $this->idList) . ')', $this->query);
|
||||
}
|
||||
else {
|
||||
if (isset($this->highwaterField['name']) && $highwater = $this->activeMigration->getHighwater()) {
|
||||
if (empty($this->highwaterField['alias'])) {
|
||||
$highwater_name = $this->highwaterField['name'];
|
||||
}
|
||||
else {
|
||||
$highwater_name = $this->highwaterField['alias'] . '.' . $this->highwaterField['name'];
|
||||
}
|
||||
$this->query = str_replace(':criteria', "$highwater_name > '$highwater'", $this->query);
|
||||
}
|
||||
else {
|
||||
// No idlist or highwater. Replace :criteria placeholder with harmless WHERE
|
||||
// clause instead of empty since we don't know if an AND follows.
|
||||
$this->query = str_replace(':criteria', '1=1', $this->query);
|
||||
}
|
||||
}
|
||||
|
||||
migrate_instrument_start('oracle_query');
|
||||
$this->connect();
|
||||
$this->result = oci_parse($this->connection, $this->query);
|
||||
if (!$this->result) {
|
||||
$e = oci_error($this->connection);
|
||||
throw new Exception($e['message'] . "\n" . $e['sqltext']);
|
||||
}
|
||||
$status = oci_execute($this->result);
|
||||
if (!$status) {
|
||||
$e = oci_error($this->result);
|
||||
throw new Exception($e['message'] . "\n" . $e['sqltext']);
|
||||
}
|
||||
migrate_instrument_stop('oracle_query');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::getNextRow().
|
||||
*
|
||||
* Returns the next row of the result set as an object, making sure NULLs are
|
||||
* represented as PHP NULLs and that LOBs are returned directly without special
|
||||
* handling.
|
||||
*/
|
||||
public function getNextRow() {
|
||||
$row = oci_fetch_array($this->result, OCI_ASSOC | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
|
||||
if (!empty($row)) {
|
||||
return (object)$row;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
337
sites/all/modules/migrate/plugins/sources/sql.inc
Normal file
337
sites/all/modules/migrate/plugins/sources/sql.inc
Normal file
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Define a MigrateSource for importing from Drupal connections
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource, to handle imports from Drupal connections.
|
||||
*/
|
||||
class MigrateSourceSQL extends MigrateSource {
|
||||
/**
|
||||
* The SQL query objects from which to obtain data, and counts of data
|
||||
*
|
||||
* @var SelectQueryInterface
|
||||
*/
|
||||
protected $originalQuery, $query, $countQuery;
|
||||
|
||||
/**
|
||||
* The result object from executing the query - traversed to process the
|
||||
* incoming data.
|
||||
*
|
||||
* @var DatabaseStatementInterface
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* Number of eligible rows processed so far (used for itemlimit checking)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $numProcessed = 0;
|
||||
|
||||
/**
|
||||
* List of available source fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* If the map is a MigrateSQLMap, and the table is compatible with the
|
||||
* source query, we can join directly to the map and make things much faster
|
||||
* and simpler.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $mapJoinable = FALSE;
|
||||
// Dynamically set whether the map is joinable - not really for production use,
|
||||
// this is primarily to support simpletests
|
||||
public function setMapJoinable($map_joinable) {
|
||||
$this->mapJoinable = $map_joinable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this source is configured to use a highwater mark, and there is
|
||||
* a highwater mark present to use.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $usingHighwater = FALSE;
|
||||
|
||||
/**
|
||||
* Whether, in the current iteration, we have reached the highwater mark.
|
||||
*
|
||||
* @var boolen
|
||||
*/
|
||||
protected $highwaterSeen = FALSE;
|
||||
|
||||
/**
|
||||
* Return an options array for PDO sources.
|
||||
*
|
||||
* @param boolean $map_joinable
|
||||
* Indicates whether the map table can be joined directly to the source query.
|
||||
* @param boolean $cache_counts
|
||||
* Indicates whether to cache counts of source records.
|
||||
*/
|
||||
static public function options($map_joinable, $cache_counts) {
|
||||
return compact('map_joinable', 'cache_counts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple initialization.
|
||||
*
|
||||
* @param SelectQueryInterface $query
|
||||
* The query we are iterating over.
|
||||
* @param array $fields
|
||||
* Optional - keys are field names, values are descriptions. Use to override
|
||||
* the default descriptions, or to add additional source fields which the
|
||||
* migration will add via other means (e.g., prepareRow()).
|
||||
* @param SelectQueryInterface $count_query
|
||||
* Optional - an explicit count query, primarily used when counting the
|
||||
* primary query is slow.
|
||||
* @param boolean $options
|
||||
* Options applied to this source.
|
||||
*/
|
||||
public function __construct(SelectQueryInterface $query, array $fields = array(),
|
||||
SelectQueryInterface $count_query = NULL, array $options = array()) {
|
||||
parent::__construct($options);
|
||||
$this->originalQuery = $query;
|
||||
$this->query = clone $query;
|
||||
$this->fields = $fields;
|
||||
if (is_null($count_query)) {
|
||||
$this->countQuery = clone $query->countQuery();
|
||||
}
|
||||
else {
|
||||
$this->countQuery = $count_query;
|
||||
}
|
||||
|
||||
if (isset($options['map_joinable'])) {
|
||||
$this->mapJoinable = $options['map_joinable'];
|
||||
}
|
||||
else {
|
||||
// TODO: We want to automatically determine if the map table can be joined
|
||||
// directly to the query, but this won't work unless/until
|
||||
// http://drupal.org/node/802514 is committed, assume joinable for now
|
||||
$this->mapJoinable = TRUE;
|
||||
/* // To be able to join the map directly, it must be a PDO map on the same
|
||||
// connection, or a compatible connection
|
||||
$map = $migration->getMap();
|
||||
if (is_a($map, 'MigrateSQLMap')) {
|
||||
$map_options = $map->getConnection()->getConnectionOptions();
|
||||
$query_options = $this->query->connection()->getConnectionOptions();
|
||||
|
||||
// Identical options means it will work
|
||||
if ($map_options == $query_options) {
|
||||
$this->mapJoinable = TRUE;
|
||||
}
|
||||
else {
|
||||
// Otherwise, the one scenario we know will work is if it's MySQL and
|
||||
// the credentials match (SQLite too?)
|
||||
if ($map_options['driver'] == 'mysql' && $query_options['driver'] == 'mysql') {
|
||||
if ($map_options['host'] == $query_options['host'] &&
|
||||
$map_options['port'] == $query_options['port'] &&
|
||||
$map_options['username'] == $query_options['username'] &&
|
||||
$map_options['password'] == $query_options['password']) {
|
||||
$this->mapJoinable = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a string representing the source query.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return (string) $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of fields available to be mapped from the source query.
|
||||
*
|
||||
* @return array
|
||||
* Keys: machine names of the fields (to be passed to addFieldMapping)
|
||||
* Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = array();
|
||||
$queryFields = $this->query->getFields();
|
||||
|
||||
if ($queryFields) {
|
||||
// Not much we can do in terms of describing the fields without manual intervention
|
||||
foreach ($queryFields as $field_name => $field_info) {
|
||||
// Lower case, because Drupal forces lowercase on fetch
|
||||
$fields[drupal_strtolower($field_name)] = drupal_strtolower(
|
||||
$field_info['table'] . '.' . $field_info['field']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Detect available fields
|
||||
$detection_query = clone $this->query;
|
||||
$result = $detection_query->range(0, 1)->execute();
|
||||
$row = $result->fetchAssoc();
|
||||
if (is_array($row)) {
|
||||
foreach ($row as $field_name => $field_value) {
|
||||
// Lower case, because Drupal forces lowercase on fetch
|
||||
$fields[drupal_strtolower($field_name)] = t('Example Content: !value',
|
||||
array('!value' => $field_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle queries without explicit field lists
|
||||
* TODO: Waiting on http://drupal.org/node/814312
|
||||
$info = Database::getConnectionInfo($query->getConnection());
|
||||
$database = $info['default']['database'];
|
||||
foreach ($this->query->getTables() as $table) {
|
||||
if (isset($table['all_fields']) && $table['all_fields']) {
|
||||
|
||||
$database = 'plants';
|
||||
$table = $table['table'];
|
||||
$sql = 'SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema=:database AND table_name = :table
|
||||
ORDER BY ordinal_position';
|
||||
$result = dbtng_query($sql, array(':database' => $database, ':table' => $table));
|
||||
foreach ($result as $row) {
|
||||
$fields[drupal_strtolower($row->column_name)] = drupal_strtolower(
|
||||
$table . '.' . $row->column_name);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
$expressionFields = $this->query->getExpressions();
|
||||
foreach ($expressionFields as $field_name => $field_info) {
|
||||
// Lower case, because Drupal forces lowercase on fetch
|
||||
$fields[drupal_strtolower($field_name)] = drupal_strtolower($field_info['alias']);
|
||||
}
|
||||
|
||||
// Any caller-specified fields with the same names as extracted fields will
|
||||
// override them; any others will be added
|
||||
if ($this->fields) {
|
||||
$fields = $this->fields + $fields;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of all available source records.
|
||||
*/
|
||||
public function computeCount() {
|
||||
$count = $this->countQuery->execute()->fetchField();
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::performRewind().
|
||||
*
|
||||
* We could simply execute the query and be functionally correct, but
|
||||
* we will take advantage of the PDO-based API to optimize the query up-front.
|
||||
*/
|
||||
public function performRewind() {
|
||||
$this->result = NULL;
|
||||
$this->query = clone $this->originalQuery;
|
||||
|
||||
// Get the key values, for potential use in joining to the map table, or
|
||||
// enforcing idlist.
|
||||
$keys = array();
|
||||
foreach ($this->activeMap->getSourceKey() as $field_name => $field_schema) {
|
||||
if (isset($field_schema['alias'])) {
|
||||
$field_name = $field_schema['alias'] . '.' . $field_name;
|
||||
}
|
||||
$keys[] = $field_name;
|
||||
}
|
||||
|
||||
// The rules for determining what conditions to add to the query are as
|
||||
// follows (applying first applicable rule)
|
||||
// 1. If idlist is provided, then only process items in that list (AND key
|
||||
// IN (idlist)). Only applicable with single-value keys.
|
||||
if ($this->idList) {
|
||||
$this->query->condition($keys[0], $this->idList, 'IN');
|
||||
}
|
||||
else {
|
||||
// 2. If the map is joinable, join it. We will want to accept all rows
|
||||
// which are either not in the map, or marked in the map as NEEDS_UPDATE.
|
||||
// Note that if highwater fields are in play, we want to accept all rows
|
||||
// above the highwater mark in addition to those selected by the map
|
||||
// conditions, so we need to OR them together (but AND with any existing
|
||||
// conditions in the query). So, ultimately the SQL condition will look
|
||||
// like (original conditions) AND (map IS NULL OR map needs update
|
||||
// OR above highwater).
|
||||
$conditions = db_or();
|
||||
$condition_added = FALSE;
|
||||
if ($this->mapJoinable) {
|
||||
// Build the join to the map table. Because the source key could have
|
||||
// multiple fields, we need to build things up.
|
||||
$count = 1;
|
||||
|
||||
foreach ($this->activeMap->getSourceKey() as $field_name => $field_schema) {
|
||||
if (isset($field_schema['alias'])) {
|
||||
$field_name = $field_schema['alias'] . '.' . $field_name;
|
||||
}
|
||||
$map_key = 'sourceid' . $count++;
|
||||
if (!isset($map_join)) {
|
||||
$map_join = '';
|
||||
}
|
||||
else {
|
||||
$map_join .= ' AND ';
|
||||
}
|
||||
$map_join .= "$field_name = map.$map_key";
|
||||
}
|
||||
|
||||
$alias = $this->query->leftJoin($this->activeMap->getQualifiedMapTable(),
|
||||
'map', $map_join);
|
||||
$conditions->isNull($alias . '.sourceid1');
|
||||
$conditions->condition($alias . '.needs_update', MigrateMap::STATUS_NEEDS_UPDATE);
|
||||
$condition_added = TRUE;
|
||||
|
||||
// And as long as we have the map table, add its data to the row.
|
||||
$count = 1;
|
||||
foreach ($this->activeMap->getSourceKey() as $field_name => $field_schema) {
|
||||
$map_key = 'sourceid' . $count++;
|
||||
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
|
||||
}
|
||||
$count = 1;
|
||||
foreach ($this->activeMap->getDestinationKey() as $field_name => $field_schema) {
|
||||
$map_key = 'destid' . $count++;
|
||||
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
|
||||
}
|
||||
$this->query->addField($alias, 'needs_update', 'migrate_map_needs_update');
|
||||
}
|
||||
// 3. If we are using highwater marks, also include rows above the mark.
|
||||
if (isset($this->highwaterField['name'])) {
|
||||
if (isset($this->highwaterField['alias'])) {
|
||||
$highwater = $this->highwaterField['alias'] . '.' . $this->highwaterField['name'];
|
||||
}
|
||||
else {
|
||||
$highwater = $this->highwaterField['name'];
|
||||
}
|
||||
$conditions->condition($highwater, $this->activeMigration->getHighwater(), '>');
|
||||
$condition_added = TRUE;
|
||||
}
|
||||
if ($condition_added) {
|
||||
$this->query->condition($conditions);
|
||||
}
|
||||
}
|
||||
|
||||
migrate_instrument_start('MigrateSourceSQL execute');
|
||||
$this->result = $this->query->execute();
|
||||
migrate_instrument_stop('MigrateSourceSQL execute');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of MigrateSource::getNextRow().
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getNextRow() {
|
||||
return $this->result->fetchObject();
|
||||
}
|
||||
}
|
||||
622
sites/all/modules/migrate/plugins/sources/sqlmap.inc
Normal file
622
sites/all/modules/migrate/plugins/sources/sqlmap.inc
Normal file
@@ -0,0 +1,622 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Defines a Drupal db-based implementation of MigrateMap.
|
||||
*/
|
||||
|
||||
class MigrateSQLMap extends MigrateMap {
|
||||
/**
|
||||
* Names of tables created for tracking the migration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mapTable, $messageTable;
|
||||
public function getMapTable() {
|
||||
return $this->mapTable;
|
||||
}
|
||||
public function getMessageTable() {
|
||||
return $this->messageTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualifying the map table name with the database name makes cross-db joins
|
||||
* possible. Note that, because prefixes are applied after we do this (i.e.,
|
||||
* it will prefix the string we return), we do not qualify the table if it has
|
||||
* a prefix. This will work fine when the source data is in the default
|
||||
* (prefixed) database (in particular, for simpletest), but not if the primary
|
||||
* query is in an external database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedMapTable() {
|
||||
$options = $this->connection->getConnectionOptions();
|
||||
$prefix = $this->connection->tablePrefix($this->mapTable);
|
||||
if ($prefix) {
|
||||
return $this->mapTable;
|
||||
}
|
||||
else {
|
||||
return $options['database'] . '.' . $this->mapTable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sourceKey and destinationKey arrays are keyed by the field names; values
|
||||
* are the Drupal schema definition for the field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function getSourceKey() {
|
||||
return $this->sourceKey;
|
||||
}
|
||||
public function getDestinationKey() {
|
||||
return $this->destinationKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drupal connection object on which to create the map/message tables
|
||||
* @var DatabaseConnection
|
||||
*/
|
||||
protected $connection;
|
||||
public function getConnection() {
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't need to check the tables more than once per request.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $ensured;
|
||||
|
||||
public function __construct($machine_name, array $source_key,
|
||||
array $destination_key, $connection_key = 'default') {
|
||||
// Default generated table names, limited to 63 characters
|
||||
$this->mapTable = 'migrate_map_' . drupal_strtolower($machine_name);
|
||||
$this->mapTable = drupal_substr($this->mapTable, 0, 63);
|
||||
$this->messageTable = 'migrate_message_' . drupal_strtolower($machine_name);
|
||||
$this->messageTable = drupal_substr($this->messageTable, 0, 63);
|
||||
$this->sourceKey = $source_key;
|
||||
$this->destinationKey = $destination_key;
|
||||
$this->connection = Database::getConnection('default', $connection_key);
|
||||
// Build the source and destination key maps
|
||||
$this->sourceKeyMap = array();
|
||||
$count = 1;
|
||||
foreach ($source_key as $field => $schema) {
|
||||
$this->sourceKeyMap[$field] = 'sourceid' . $count++;
|
||||
}
|
||||
$this->destinationKeyMap = array();
|
||||
$count = 1;
|
||||
foreach ($destination_key as $field => $schema) {
|
||||
$this->destinationKeyMap[$field] = 'destid' . $count++;
|
||||
}
|
||||
$this->ensureTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the map and message tables if they don't already exist.
|
||||
*/
|
||||
protected function ensureTables() {
|
||||
if (!$this->ensured) {
|
||||
if (!$this->connection->schema()->tableExists($this->mapTable)) {
|
||||
// Generate appropriate schema info for the map and message tables,
|
||||
// and map from the source field names to the map/msg field names
|
||||
$count = 1;
|
||||
$source_key_schema = array();
|
||||
$pks = array();
|
||||
foreach ($this->sourceKey as $field_schema) {
|
||||
$mapkey = 'sourceid' . $count++;
|
||||
$source_key_schema[$mapkey] = $field_schema;
|
||||
$pks[] = $mapkey;
|
||||
}
|
||||
|
||||
$fields = $source_key_schema;
|
||||
|
||||
// Add destination keys to map table
|
||||
// TODO: How do we discover the destination schema?
|
||||
$count = 1;
|
||||
foreach ($this->destinationKey as $field_schema) {
|
||||
// Allow dest key fields to be NULL (for IGNORED/FAILED cases)
|
||||
$field_schema['not null'] = FALSE;
|
||||
$mapkey = 'destid' . $count++;
|
||||
$fields[$mapkey] = $field_schema;
|
||||
}
|
||||
$fields['needs_update'] = array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => MigrateMap::STATUS_IMPORTED,
|
||||
'description' => 'Indicates current status of the source row',
|
||||
);
|
||||
$fields['last_imported'] = array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'UNIX timestamp of the last time this row was imported',
|
||||
);
|
||||
$schema = array(
|
||||
'description' => t('Mappings from source key to destination key'),
|
||||
'fields' => $fields,
|
||||
'primary key' => $pks,
|
||||
);
|
||||
$this->connection->schema()->createTable($this->mapTable, $schema);
|
||||
|
||||
// Now for the message table
|
||||
$fields = array();
|
||||
$fields['msgid'] = array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$fields += $source_key_schema;
|
||||
|
||||
$fields['level'] = array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
);
|
||||
$fields['message'] = array(
|
||||
'type' => 'text',
|
||||
'size' => 'medium',
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$schema = array(
|
||||
'description' => t('Messages generated during a migration process'),
|
||||
'fields' => $fields,
|
||||
'primary key' => array('msgid'),
|
||||
'indexes' => array('sourcekey' => $pks),
|
||||
);
|
||||
$this->connection->schema()->createTable($this->messageTable, $schema);
|
||||
}
|
||||
$this->ensured = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a row from the map table, given a source ID
|
||||
*
|
||||
* @param array $source_id
|
||||
*/
|
||||
public function getRowBySource(array $source_id) {
|
||||
migrate_instrument_start('mapRowBySource');
|
||||
$query = $this->connection->select($this->mapTable, 'map')
|
||||
->fields('map');
|
||||
foreach ($this->sourceKeyMap as $key_name) {
|
||||
$query = $query->condition("map.$key_name", array_shift($source_id), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
migrate_instrument_stop('mapRowBySource');
|
||||
return $result->fetchAssoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a row from the map table, given a destination ID
|
||||
*
|
||||
* @param array $source_id
|
||||
*/
|
||||
public function getRowByDestination(array $destination_id) {
|
||||
migrate_instrument_start('mapRowByDestination');
|
||||
$query = $this->connection->select($this->mapTable, 'map')
|
||||
->fields('map');
|
||||
foreach ($this->destinationKeyMap as $key_name) {
|
||||
$query = $query->condition("map.$key_name", array_shift($destination_id), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
migrate_instrument_stop('mapRowByDestination');
|
||||
return $result->fetchAssoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an array of map rows marked as needing update.
|
||||
*
|
||||
* @param int $count
|
||||
* Maximum rows to return; defaults to 10,000
|
||||
* @return array
|
||||
* Array of map row objects with needs_update==1.
|
||||
*/
|
||||
public function getRowsNeedingUpdate($count) {
|
||||
$rows = array();
|
||||
$result = db_select($this->mapTable, 'map')
|
||||
->fields('map')
|
||||
->condition('needs_update', MigrateMap::STATUS_NEEDS_UPDATE)
|
||||
->range(0, $count)
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a (possibly multi-field) destination key, return the (possibly multi-field)
|
||||
* source key mapped to it.
|
||||
*
|
||||
* @param array $destination_id
|
||||
* Array of destination key values.
|
||||
* @return array
|
||||
* Array of source key values, or NULL on failure.
|
||||
*/
|
||||
public function lookupSourceID(array $destination_id) {
|
||||
migrate_instrument_start('lookupSourceID');
|
||||
$query = $this->connection->select($this->mapTable, 'map')
|
||||
->fields('map', $this->sourceKeyMap);
|
||||
foreach ($this->destinationKeyMap as $key_name) {
|
||||
$query = $query->condition("map.$key_name", array_shift($destination_id), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
$source_id = $result->fetchAssoc();
|
||||
migrate_instrument_stop('lookupSourceID');
|
||||
return $source_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a (possibly multi-field) source key, return the (possibly multi-field)
|
||||
* destination key it is mapped to.
|
||||
*
|
||||
* @param array $source_id
|
||||
* Array of source key values.
|
||||
* @return array
|
||||
* Array of destination key values, or NULL on failure.
|
||||
*/
|
||||
public function lookupDestinationID(array $source_id) {
|
||||
migrate_instrument_start('lookupDestinationID');
|
||||
$query = $this->connection->select($this->mapTable, 'map')
|
||||
->fields('map', $this->destinationKeyMap);
|
||||
foreach ($this->sourceKeyMap as $key_name) {
|
||||
$query = $query->condition("map.$key_name", array_shift($source_id), '=');
|
||||
}
|
||||
$result = $query->execute();
|
||||
$destination_id = $result->fetchAssoc();
|
||||
migrate_instrument_stop('lookupDestinationID');
|
||||
return $destination_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon successful import of one record, we record a mapping from
|
||||
* the source key to the destination key. Also may be called, setting the
|
||||
* third parameter to NEEDS_UPDATE, to signal an existing record should be remigrated.
|
||||
*
|
||||
* @param stdClass $source_row
|
||||
* The raw source data. We use the key map derived from the source object
|
||||
* to get the source key values.
|
||||
* @param array $dest_ids
|
||||
* The destination key values.
|
||||
* @param int $needs_update
|
||||
* Status of the source row in the map. Defaults to STATUS_IMPORTED.
|
||||
*/
|
||||
public function saveIDMapping(stdClass $source_row, array $dest_ids, $needs_update = MigrateMap::STATUS_IMPORTED) {
|
||||
migrate_instrument_start('saveIDMapping');
|
||||
// Construct the source key
|
||||
$keys = array();
|
||||
foreach ($this->sourceKeyMap as $field_name => $key_name) {
|
||||
// A NULL key value will fail.
|
||||
if (is_null($source_row->$field_name)) {
|
||||
Migration::displayMessage(t(
|
||||
'Could not save to map table due to NULL value for key field !field',
|
||||
array('!field' => $field_name)));
|
||||
migrate_instrument_stop('saveIDMapping');
|
||||
return;
|
||||
}
|
||||
$keys[$key_name] = $source_row->$field_name;
|
||||
}
|
||||
|
||||
$fields = array('needs_update' => (int)$needs_update);
|
||||
$count = 1;
|
||||
foreach ($dest_ids as $dest_id) {
|
||||
$fields['destid' . $count++] = $dest_id;
|
||||
}
|
||||
if ($this->trackLastImported) {
|
||||
$fields['last_imported'] = time();
|
||||
}
|
||||
$this->connection->merge($this->mapTable)
|
||||
->key($keys)
|
||||
->fields($fields)
|
||||
->execute();
|
||||
migrate_instrument_stop('saveIDMapping');
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a message in the migration's message table.
|
||||
*
|
||||
* @param array $source_key
|
||||
* Source ID of the record in error
|
||||
* @param string $message
|
||||
* The message to record.
|
||||
* @param int $level
|
||||
* Optional message severity (defaults to MESSAGE_ERROR).
|
||||
*/
|
||||
public function saveMessage($source_key, $message, $level = Migration::MESSAGE_ERROR) {
|
||||
// Source IDs as arguments
|
||||
$count = 1;
|
||||
if (is_array($source_key)) {
|
||||
foreach ($source_key as $key_value) {
|
||||
$fields['sourceid' . $count++] = $key_value;
|
||||
// If any key value is empty, we can't save - print out and abort
|
||||
if (empty($key_value)) {
|
||||
print($message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$fields['level'] = $level;
|
||||
$fields['message'] = $message;
|
||||
$this->connection->insert($this->messageTable)
|
||||
->fields($fields)
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
// TODO: What else can we do?
|
||||
Migration::displayMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares this migration to run as an update - that is, in addition to
|
||||
* unmigrated content (source records not in the map table) being imported,
|
||||
* previously-migrated content will also be updated in place.
|
||||
*/
|
||||
public function prepareUpdate() {
|
||||
$this->connection->update($this->mapTable)
|
||||
->fields(array('needs_update' => MigrateMap::STATUS_NEEDS_UPDATE))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of records in the map table (i.e., the number of
|
||||
* source records which have been processed for this migration).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function processedCount() {
|
||||
$query = $this->connection->select($this->mapTable);
|
||||
$query->addExpression('COUNT(*)', 'count');
|
||||
$count = $query->execute()->fetchField();
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of imported records in the map table.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function importedCount() {
|
||||
$query = $this->connection->select($this->mapTable);
|
||||
$query->addExpression('COUNT(*)', 'count');
|
||||
$query->condition('needs_update', array(MigrateMap::STATUS_IMPORTED, MigrateMap::STATUS_NEEDS_UPDATE), 'IN');
|
||||
$count = $query->execute()->fetchField();
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of records which are marked as needing update.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function updateCount() {
|
||||
$query = $this->connection->select($this->mapTable);
|
||||
$query->addExpression('COUNT(*)', 'count');
|
||||
$query->condition('needs_update', MigrateMap::STATUS_NEEDS_UPDATE);
|
||||
$count = $query->execute()->fetchField();
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of source records which failed to import.
|
||||
*
|
||||
* @return int
|
||||
* Number of records errored out.
|
||||
*/
|
||||
public function errorCount() {
|
||||
$query = $this->connection->select($this->mapTable);
|
||||
$query->addExpression('COUNT(*)', 'count');
|
||||
$query->condition('needs_update', MigrateMap::STATUS_FAILED);
|
||||
$count = $query->execute()->fetchField();
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of messages saved.
|
||||
*
|
||||
* @return int
|
||||
* Number of messages.
|
||||
*/
|
||||
public function messageCount() {
|
||||
$query = $this->connection->select($this->messageTable);
|
||||
$query->addExpression('COUNT(*)', 'count');
|
||||
$count = $query->execute()->fetchField();
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the map entry and any message table entries for the specified source row.
|
||||
*
|
||||
* @param array $source_key
|
||||
*/
|
||||
public function delete(array $source_key, $messages_only = FALSE) {
|
||||
if (!$messages_only) {
|
||||
$map_query = $this->connection->delete($this->mapTable);
|
||||
}
|
||||
$message_query = $this->connection->delete($this->messageTable);
|
||||
$count = 1;
|
||||
foreach ($source_key as $key_value) {
|
||||
if (!$messages_only) {
|
||||
$map_query->condition('sourceid' . $count, $key_value);
|
||||
}
|
||||
$message_query->condition('sourceid' . $count, $key_value);
|
||||
$count++;
|
||||
}
|
||||
|
||||
if (!$messages_only) {
|
||||
$map_query->execute();
|
||||
}
|
||||
$message_query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the map entry and any message table entries for the specified destination row.
|
||||
*
|
||||
* @param array $destination_key
|
||||
*/
|
||||
public function deleteDestination(array $destination_key) {
|
||||
$map_query = $this->connection->delete($this->mapTable);
|
||||
$message_query = $this->connection->delete($this->messageTable);
|
||||
$source_key = $this->lookupSourceID($destination_key);
|
||||
if (!empty($source_key)) {
|
||||
$count = 1;
|
||||
foreach ($destination_key as $key_value) {
|
||||
$map_query->condition('destid' . $count, $key_value);
|
||||
$count++;
|
||||
}
|
||||
$map_query->execute();
|
||||
$count = 1;
|
||||
foreach ($source_key as $key_value) {
|
||||
$message_query->condition('sourceid' . $count, $key_value);
|
||||
$count++;
|
||||
}
|
||||
$message_query->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specified row to be updated, if it exists.
|
||||
*/
|
||||
public function setUpdate(array $source_key) {
|
||||
$query = $this->connection->update($this->mapTable)
|
||||
->fields(array('needs_update' => MigrateMap::STATUS_NEEDS_UPDATE));
|
||||
$count = 1;
|
||||
foreach ($source_key as $key_value) {
|
||||
$query->condition('sourceid' . $count++, $key_value);
|
||||
}
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all map and message table entries specified.
|
||||
*
|
||||
* @param array $source_keys
|
||||
* Each array member is an array of key fields for one source row.
|
||||
*/
|
||||
public function deleteBulk(array $source_keys) {
|
||||
// If we have a single-column key, we can shortcut it
|
||||
if (count($this->sourceKey) == 1) {
|
||||
$sourceids = array();
|
||||
foreach ($source_keys as $source_key) {
|
||||
$sourceids[] = $source_key;
|
||||
}
|
||||
$this->connection->delete($this->mapTable)
|
||||
->condition('sourceid1', $sourceids, 'IN')
|
||||
->execute();
|
||||
$this->connection->delete($this->messageTable)
|
||||
->condition('sourceid1', $sourceids, 'IN')
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
foreach ($source_keys as $source_key) {
|
||||
$map_query = $this->connection->delete($this->mapTable);
|
||||
$message_query = $this->connection->delete($this->messageTable);
|
||||
$count = 1;
|
||||
foreach ($source_key as $key_value) {
|
||||
$map_query->condition('sourceid' . $count, $key_value);
|
||||
$message_query->condition('sourceid' . $count++, $key_value);
|
||||
}
|
||||
$map_query->execute();
|
||||
$message_query->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all messages from the message table.
|
||||
*/
|
||||
public function clearMessages() {
|
||||
$this->connection->truncate($this->messageTable)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the associated map and message tables.
|
||||
*/
|
||||
public function destroy() {
|
||||
$this->connection->schema()->dropTable($this->mapTable);
|
||||
$this->connection->schema()->dropTable($this->messageTable);
|
||||
}
|
||||
|
||||
protected $result = NULL;
|
||||
protected $currentRow = NULL;
|
||||
protected $currentKey = array();
|
||||
public function getCurrentKey() {
|
||||
return $this->currentKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::rewind() - called before beginning a foreach loop.
|
||||
* TODO: Support idlist, itemlimit
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->currentRow = NULL;
|
||||
$fields = array();
|
||||
foreach ($this->sourceKeyMap as $field) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
foreach ($this->destinationKeyMap as $field) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
if (isset($this->options['itemlimit'])) {
|
||||
$query = $query->range(0, $this->options['itemlimit']);
|
||||
}
|
||||
*/
|
||||
$this->result = $this->connection->select($this->mapTable, 'map')
|
||||
->fields('map', $fields)
|
||||
->execute();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::current() - called when entering a loop
|
||||
* iteration, returning the current row
|
||||
*/
|
||||
public function current() {
|
||||
return $this->currentRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::key - called when entering a loop iteration, returning
|
||||
* the key of the current row. It must be a scalar - we will serialize
|
||||
* to fulfill the requirement, but using getCurrentKey() is preferable.
|
||||
*/
|
||||
public function key() {
|
||||
return serialize($this->currentKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::next() - called at the bottom of the loop implicitly,
|
||||
* as well as explicitly from rewind().
|
||||
*/
|
||||
public function next() {
|
||||
$this->currentRow = $this->result->fetchObject();
|
||||
$this->currentKey = array();
|
||||
if (!is_object($this->currentRow)) {
|
||||
$this->currentRow = NULL;
|
||||
}
|
||||
else {
|
||||
foreach ($this->sourceKeyMap as $map_field) {
|
||||
$this->currentKey[$map_field] = $this->currentRow->$map_field;
|
||||
// Leave only destination fields
|
||||
unset($this->currentRow->$map_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::valid() - called at the top of the loop, returning
|
||||
* TRUE to process the loop and FALSE to terminate it
|
||||
*/
|
||||
public function valid() {
|
||||
// TODO: Check numProcessed against itemlimit
|
||||
return !is_null($this->currentRow);
|
||||
}
|
||||
}
|
||||
1067
sites/all/modules/migrate/plugins/sources/xml.inc
Normal file
1067
sites/all/modules/migrate/plugins/sources/xml.inc
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user