first import

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
<?php
/**
* @file
* Defines base for migration destinations.
*/
/**
* Abstract base class for destination handling.
*
* Derived classes are expected to define __toString(), returning a string
* describing the type of destination and significant options. See
* MigrateDestinationEntity for an example.
*/
abstract class MigrateDestination {
/**
* To support MigrateSQLMap maps, derived destination classes should return
* schema field definition(s) corresponding to the primary key of the destination
* being implemented. These are used to construct the destination key fields
* of the map table for a migration using this destination.
*
* abstract static public function getKeySchema()
*/
/**
* Derived classes must implement __toString().
*
* @return string
* Description of the destination being migrated into
*/
abstract public function __toString();
/**
* Derived classes must implement fields(), returning a list of available
* destination fields.
*
* @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.
*/
abstract public function fields();
/**
* Derived classes must implement either bulkRollback or rollback() according to
* the signatures below, to rollback (usually by deletion) previously-migrated
* items.
*
* $ids is an array of single-field keys to be deleted
* abstract public function bulkRollback(array $ids);
*
* $key is an array of fields keying a single entry to delete
* abstract public function rollback(array $key);
*/
/**
* Derived classes must implement import(), to construct one new object (pre-pppulated
* using field mappings in the Migration). It is expected to call prepare and
* complete handlers, passing them $row (the raw data from the source).
*/
abstract public function import(stdClass $object, stdClass $row);
/**
* Derived classes may implement preImport() and/or postImport(), to do any
* processing they need done before or after looping over all source rows.
* Similarly, preRollback() or postRollback() may be implemented.
*
* abstract public function preImport();
* abstract public function postImport();
* abstract public function preRollback();
* abstract public function postRollback();
*/
/**
* Maintain stats on the number of destination objects created or updated.
*
* @var int
*/
protected $numCreated = 0;
public function getCreated() {
return $this->numCreated;
}
protected $numUpdated = 0;
public function getUpdated() {
return $this->numUpdated;
}
/**
* Reset numCreated and numUpdated back to 0.
*/
public function resetStats() {
$this->numCreated = 0;
$this->numUpdated = 0;
}
/**
* Null constructor
*/
public function __construct() {
}
}
/**
* All destination handlers should be derived from MigrateDestinationHandler
*/
abstract class MigrateDestinationHandler extends MigrateHandler {
// abstract function arguments(...)
/**
* Any one or more of these methods may be implemented
*/
//abstract public function fields();
//abstract public function prepare($entity, stdClass $row);
//abstract public function complete($entity, stdClass $row);
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* @file
* Custom exception class for the migrate module.
*/
class MigrateException extends Exception {
protected $level;
public function getLevel() {
return $this->level;
}
public function __construct($message, $level = Migration::MESSAGE_ERROR) {
$this->level = $level;
parent::__construct($message);
}
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* @file
* The MigrateFieldMapping class - tracking mappings between source and
* destination.
*/
class MigrateFieldMapping {
/**
* Destination field name for the mapping. If empty, the mapping is just a
* stub for annotating the source field.
*
* @var string
*/
protected $destinationField;
public function getDestinationField() {
return $this->destinationField;
}
/**
* Source field name for the mapping. If empty, the defaultValue will be
* applied.
*
* @var string
*/
protected $sourceField;
public function getSourceField() {
return $this->sourceField;
}
/**
* Default value for simple mappings, when there is no source mapping or the
* source field is empty. If both this and the sourceField are omitted, the
* mapping is just a stub for annotating the destination field.
*
* @var mixed
*/
protected $defaultValue;
public function getDefaultValue() {
return $this->defaultValue;
}
/**
* Separator string. If present, the destination field will be set up as an
* array of values exploded from the corresponding source field.
*
* @var string
*/
protected $separator;
public function getSeparator() {
return $this->separator;
}
/**
* Class name of source migration for a field. If present, the value in the
* source field is considered to be a source ID in the mapping table of this
* migration, and the corresponding destination ID will be retrieved.
*
* @var mixed
* An array of source migrations, or string for a single migration.
*/
protected $sourceMigration;
public function getSourceMigration() {
return $this->sourceMigration;
}
/**
* Array of callbacks to be called on a source value.
*
* @var string
*/
protected $callbacks = array();
public function getCallbacks() {
return $this->callbacks;
}
/**
* An associative array with keys:
* - table: The table for querying for a duplicate.
* - column: The column for querying for a duplicate.
*
* @todo: Let fields declare this data and a replacement pattern. Then
* developers won't have to specify this.
*
* @var string
*/
protected $dedupe;
public function getDedupe() {
return $this->dedupe;
}
/**
* Argument overrides. If present this will be an array, keyed by
* a field API array key, with one or both of these entries:
* 'source_field' - Name of the source field in the incoming row containing the
* value to be assigned
* 'default_value' - A constant value to be assigned in the absence of source_field
*
* @var array
*/
protected $arguments;
public function getArguments() {
return $this->arguments;
}
protected $description = '';
public function getDescription() {
return $this->description;
}
protected $issueGroup;
public function getIssueGroup() {
return $this->issueGroup;
}
protected $issueNumber;
public function getIssueNumber() {
return $this->issueNumber;
}
protected $issuePriority = self::ISSUE_PRIORITY_OK;
public function getIssuePriority() {
return $this->issuePriority;
}
const ISSUE_PRIORITY_OK = 1;
const ISSUE_PRIORITY_LOW = 2;
const ISSUE_PRIORITY_MEDIUM = 3;
const ISSUE_PRIORITY_BLOCKER = 4;
public static $priorities = array();
public function __construct($destination_field, $source_field) {
// Must have one or the other
if (!$destination_field && !$source_field) {
throw new Exception('Field mappings must have a destination field or a source field');
}
$this->destinationField = $destination_field;
$this->sourceField = $source_field;
$this->issueGroup = t('Done');
if (count(self::$priorities) == 0) {
self::$priorities[self::ISSUE_PRIORITY_OK] = t('OK');
self::$priorities[self::ISSUE_PRIORITY_LOW] = t('Low');
self::$priorities[self::ISSUE_PRIORITY_MEDIUM] = t('Medium');
self::$priorities[self::ISSUE_PRIORITY_BLOCKER] = t('Blocker');
}
}
public function defaultValue($default_value) {
$this->defaultValue = $default_value;
return $this;
}
public function separator($separator) {
$this->separator = $separator;
return $this;
}
public function sourceMigration($source_migration) {
$this->sourceMigration = $source_migration;
return $this;
}
public function callbacks($callbacks) {
$this->callbacks = func_get_args();
return $this;
}
public function dedupe($table, $column) {
$this->dedupe = array('table' => $table, 'column' => $column);
return $this;
}
public function arguments($arguments) {
$this->arguments = $arguments;
return $this;
}
public function description($text) {
$this->description = $text;
return $this;
}
public function issueGroup($group) {
if (!$group) {
$group = t('Done');
}
$this->issueGroup = $group;
return $this;
}
public function issueNumber($number) {
$this->issueNumber = $number;
return $this;
}
public function issuePriority($priority) {
$this->issuePriority = $priority;
return $this;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* @file
* Definition for a migration group.
*/
class MigrateGroup {
/**
* The name of the group - used to identify it in drush commands.
*
* @var string
*/
protected $name;
public function getName() {
return $this->name;
}
/**
* List of groups this group is dependent on.
*
* @var array
*/
protected $dependencies = array();
public function getDependencies() {
return $this->dependencies;
}
/**
* The central list of all known groups, keyed by group name.
*
* @var array
*/
static protected $groupList = array();
static public function groups() {
$groups = array();
$dependent_groups = array();
$required_groups = array();
foreach (self::$groupList as $name => $group) {
$dependencies = $group->getDependencies();
if (count($dependencies) > 0) {
// Set groups with dependencies aside for reordering
$dependent_groups[$name] = $group;
$required_groups += $dependencies;
}
else {
// No dependencies, just add
$groups[$name] = $group;
}
}
$iterations = 0;
while (count($dependent_groups) > 0) {
if ($iterations++ > 20) {
$group_names = implode(',', array_keys($dependent_groups));
throw new MigrateException(t('Failure to sort migration list - most likely due ' .
'to circular dependencies involving groups !group_names',
array('!group_names' => $group_names)));
}
foreach ($dependent_groups as $name => $group) {
$ready = TRUE;
// Scan all the dependencies for this group and make sure they're all
// in the final list
foreach ($group->getDependencies() as $dependency) {
if (!isset($groups[$dependency])) {
$ready = FALSE;
break;
}
}
if ($ready) {
// Yes they are! Move this group to the final list
$groups[$name] = $group;
unset($dependent_groups[$name]);
}
}
}
return $groups;
}
/**
* Basic constructor.
*
* @param string $name
* Group name.
*
* @param array $dependencies
* List of dependent groups.
*/
public function __construct($name, $dependencies = array()) {
$this->name = $name;
$this->dependencies = $dependencies;
}
/**
* Retrieve (creating if necessary) an instance of the named group.
*
* @param string $name
* Group name.
*
* @param array $dependencies
* List of dependent groups.
*/
static public function getInstance($name, $dependencies = array()) {
if (empty(self::$groupList[$name])) {
self::$groupList[$name] = new MigrateGroup($name, $dependencies);
}
return self::$groupList[$name];
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* @file
* Defines the base class for destination handlers.
*/
/**
* Abstract base class for destination handlers. Handler objects are expected
* to implement appropriate methods (e.g., prepare, complete, or fields).
*/
abstract class MigrateHandler {
/**
* List of other handler classes which should be invoked before the current one.
*
* @var array
*/
protected $dependencies = array();
public function getDependencies() {
return $this->dependencies;
}
/**
* List of "types" handled by this handler. Depending on the kind of handler,
* these may be destination types, field types, etc.
*
* @var array
*/
protected $typesHandled = array();
public function getTypesHandled() {
return $this->typesHandled;
}
/**
* Register a list of types handled by this class
*
* @param array $types
*/
protected function registerTypes(array $types) {
// Make the type names the keys
foreach ($types as $type) {
$type = drupal_strtolower($type);
$this->typesHandled[$type] = $type;
}
}
/**
* Does this handler handle the given type?
*
* @param boolean $type
*/
public function handlesType($type) {
return isset($this->typesHandled[strtolower($type)]);
}
abstract public function __construct();
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* @file
* Defines the framework for map and message handling.
*/
/**
* We implement the Iterator interface to support iteration over the map table
* for the purpose of rollback.
*/
abstract class MigrateMap implements Iterator {
/**
* Codes reflecting the current status of a map row.
*/
const STATUS_IMPORTED = 0;
const STATUS_NEEDS_UPDATE = 1;
const STATUS_IGNORED = 2;
const STATUS_FAILED = 3;
/**
* Arrays of key fields for the source and destination. Array keys are the
* field names - array values are specific to the concrete map class.
*
* @var array
*/
protected $sourceKey, $destinationKey;
abstract public function getSourceKey();
abstract public function getDestinationKey();
/**
* Mapping from field names to the map/message table key names (e.g.,
* from input_field to sourceid1, or from nid to destid1)
*
* @var array
*/
protected $sourceKeyMap, $destinationKeyMap;
/**
* Boolean determining whether to track last_imported times in map tables
*
* @var boolean
*/
protected $trackLastImported = FALSE;
/**
* Save a mapping from the key values in the source row to the destination
* keys.
*
* @param $source_row
* @param $dest_ids
* @param $status
*/
abstract public function saveIDMapping(stdClass $source_row, array $dest_ids,
$status = MigrateMap::STATUS_IMPORTED);
/**
* Record a message related to a source record
*
* @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).
*/
abstract public function saveMessage($source_key, $message, $level = MigrationBase::MESSAGE_ERROR);
/**
* Prepare to run a full update - mark all previously-imported content as
* ready to be re-imported.
*/
abstract public function prepareUpdate();
/**
* Report the number of processed items in the map
*/
abstract public function processedCount();
/**
* Report the number of imported items in the map
*/
abstract public function importedCount();
/**
* Report the number of items that failed to import
*/
abstract public function errorCount();
/**
* Report the number of messages
*/
abstract public function messageCount();
/**
* Delete the map and message entries for a given source record
*
* @param array $source_key
*/
abstract public function delete(array $source_key, $messages_only = FALSE);
/**
* Delete the map and message entries for a given destination record
*
* @param array $destination_key
*/
abstract public function deleteDestination(array $destination_key);
/**
* Delete the map and message entries for a set of given source records.
*
* @param array $source_keys
*/
abstract public function deleteBulk(array $source_keys);
/**
* Clear all messages from the map.
*/
abstract public function clearMessages();
/**
* Retrieve map data for a given source or destination item
*/
abstract public function getRowBySource(array $source_id);
abstract public function getRowByDestination(array $destination_id);
/**
* Retrieve an array of map rows marked as needing update.
*/
abstract public function getRowsNeedingUpdate($count);
/**
* 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.
*/
abstract public function lookupSourceID(array $destination_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.
*/
abstract public function lookupDestinationID(array $source_id);
/**
* Remove any persistent storage used by this map (e.g., map and message tables)
*/
abstract public function destroy();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,368 @@
<?php
/**
* @file
* Define base for migration sources.
*/
/**
* Abstract base class for source handling.
*
* Derived classes are expected to define __toString(), returning a string
* describing the source and significant options. See
* MigrateSourceSQL for an example.
*/
abstract class MigrateSource implements Iterator {
/**
* The current row from the quey
*
* @var stdClass
*/
protected $currentRow;
/**
* The primary key of the current row
*
* @var array
*/
protected $currentKey;
public function getCurrentKey() {
return $this->currentKey;
}
/**
* The Migration class currently invoking us, during rewind() and next().
*
* @var Migration
*/
protected $activeMigration;
/**
* The MigrateMap class for the current migration.
*
* @var MigrateMap
*/
protected $activeMap;
/**
* Number of rows intentionally ignored (prepareRow() returned FALSE)
*
* @var int
*/
protected $numIgnored = 0;
public function getIgnored() {
return $this->numIgnored;
}
/**
* Number of rows we've at least looked at.
*
* @var int
*/
protected $numProcessed = 0;
public function getProcessed() {
return $this->numProcessed;
}
/**
* Reset numIgnored back to 0.
*/
public function resetStats() {
$this->numIgnored = 0;
}
/**
* Information on the highwater mark for the current migration, if any.
*
* @var array
*/
protected $highwaterField;
/**
* List of source IDs to process.
*
* @var array
*/
protected $idList = array();
/**
* Derived classes must implement fields(), returning a list of available
* source fields.
*
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
abstract public function fields();
/**
* Whether this instance should cache the source count.
*
* @var boolean
*/
protected $cacheCounts = FALSE;
/**
* Key to use for caching counts.
*
* @var string
*/
protected $cacheKey;
/**
* Whether this instance should not attempt to count the source.
*
* @var boolean
*/
protected $skipCount = FALSE;
/**
* By default, next() will directly read the map row and add it to the data
* row. A source plugin implementation may do this itself (in particular, the
* SQL source can incorporate the map table into the query) - if so, it should
* set this TRUE so we don't duplicate the effort.
*
* @var bool
*/
protected $mapRowAdded = FALSE;
/**
* Return a count of available source records, from the cache if appropriate.
* Returns -1 if the source is not countable.
*
* @param boolean $refresh
*/
public function count($refresh = FALSE) {
if ($this->skipCount) {
return -1;
}
if (!isset($this->cacheKey)) {
$this->cacheKey = md5((string)$this);
}
// If a refresh is requested, or we're not caching counts, ask the derived
// class to get the count from the source.
if ($refresh || !$this->cacheCounts) {
$count = $this->computeCount();
cache_set($this->cacheKey, $count, 'cache');
}
else {
// Caching is in play, first try to retrieve a cached count.
$cache_object = cache_get($this->cacheKey, 'cache');
if (is_object($cache_object)) {
// Success
$count = $cache_object->data;
}
else {
// No cached count, ask the derived class to count 'em up, and cache
// the result
$count = $this->computeCount();
cache_set($this->cacheKey, $count, 'cache');
}
}
return $count;
}
/**
* Derived classes must implement computeCount(), to retrieve a fresh count of
* source records.
*/
//abstract public function computeCount();
/**
* Class constructor.
*
* @param array $options
* Optional array of options.
*/
public function __construct($options = array()) {
if (!empty($options['cache_counts'])) {
$this->cacheCounts = TRUE;
}
if (!empty($options['skip_count'])) {
$this->skipCount = TRUE;
}
if (!empty($options['cache_key'])) {
$this->cacheKey = $options['cache_key'];
}
}
/**
* Default implementations of Iterator methods - many derivations will find
* these adequate and will only need to implement rewind() and 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::valid() - called at the top of the loop, returning
* TRUE to process the loop and FALSE to terminate it
*/
public function valid() {
return !is_null($this->currentRow);
}
/**
* Implementation of Iterator::rewind() - subclasses of MigrateSource should
* implement performRewind() to do any class-specific setup for iterating
* source records.
*/
public function rewind() {
$this->activeMigration = Migration::currentMigration();
$this->activeMap = $this->activeMigration->getMap();
$this->numProcessed = 0;
$this->numIgnored = 0;
$this->highwaterField = $this->activeMigration->getHighwaterField();
if ($this->activeMigration->getOption('idlist')) {
$this->idList = explode(',', $this->activeMigration->getOption('idlist'));
}
else {
$this->idList = array();
}
migrate_instrument_start(get_class($this) . ' performRewind');
$this->performRewind();
migrate_instrument_stop(get_class($this) . ' performRewind');
$this->next();
}
/**
* Implementation of Iterator::next() - subclasses of MigrateSource should
* implement getNextRow() to retrieve the next valid source rocord to process.
*/
public function next() {
$this->currentKey = NULL;
$this->currentRow = NULL;
migrate_instrument_start(get_class($this) . ' getNextRow');
while ($row = $this->getNextRow()) {
migrate_instrument_stop(get_class($this) . ' getNextRow');
// Populate the source key for this row
foreach ($this->activeMap->getSourceKey() as $field_name => $field_schema) {
$this->currentKey[$field_name] = $row->$field_name;
}
// Pick up the existing map row, if any, unless getNextRow() did it.
if (!$this->mapRowAdded) {
$map_row = $this->activeMap->getRowBySource($this->currentKey);
// Add map info to the row, if present
if ($map_row) {
foreach ($map_row as $field => $value) {
$field = 'migrate_map_' . $field;
$row->$field = $value;
}
}
}
// First, determine if this row should be passed to prepareRow(), or skipped
// entirely. The rules are:
// 1. If there's an explicit idlist, that's all we care about (ignore
// highwaters and map rows).
$prepared = FALSE;
if (!empty($this->idList)) {
if (in_array(reset($this->currentKey), $this->idList)) {
// In the list, fall through.
}
else {
// Not in the list, skip it
$this->currentRow = NULL;
continue;
}
}
// 2. If the row is not in the map (we have never tried to import it before),
// we always want to try it.
elseif (!isset($row->migrate_map_sourceid1)) {
// Fall through
}
// 3. If the row is marked as needing update, pass it.
elseif ($row->migrate_map_needs_update == MigrateMap::STATUS_NEEDS_UPDATE) {
// Fall through
}
// 4. At this point, we have a row which has previously been imported and
// not marked for update. If we're not using highwater marks, then we
// will not take this row.
elseif (empty($this->highwaterField)) {
// No highwater, skip
$this->currentRow = NULL;
continue;
}
// 5. So, we are using highwater marks. Take the row if its highwater field
// value is greater than the saved marked, otherwise skip it.
else {
// Call prepareRow() here, in case the highwaterField needs preparation
if ($this->prepareRow($row) !== FALSE) {
if ($row->{$this->highwaterField['name']} > $this->activeMigration->getHighwater()) {
$this->currentRow = $row;
break;
}
else {
// Skip
$this->currentRow = NULL;
continue;
}
}
else {
$this->currentRow = NULL;
}
$prepared = TRUE;
}
// Allow the Migration to prepare this row. prepareRow() can return boolean
// FALSE to ignore this row.
if (!$prepared) {
if ($this->prepareRow($row) !== FALSE) {
// Finally, we've got a keeper.
$this->currentRow = $row;
break;
}
else {
$this->currentRow = NULL;
}
}
}
migrate_instrument_stop(get_class($this) . ' getNextRow');
if (!$this->currentRow) {
$this->currentKey = NULL;
}
}
/**
* Give the calling migration a shot at manipulating, and possibly rejecting,
* the source row.
*
* @return bool
* FALSE if the row is to be skipped.
*/
protected function prepareRow($row) {
migrate_instrument_start(get_class($this->activeMigration) . ' prepareRow');
$return = $this->activeMigration->prepareRow($row);
migrate_instrument_stop(get_class($this->activeMigration) . ' prepareRow');
// We're explicitly skipping this row - keep track in the map table
if ($return === FALSE) {
$this->activeMigration->getMap()->saveIDMapping($row, array(NULL),
MigrateMap::STATUS_IGNORED);
$this->numIgnored++;
$this->currentRow = NULL;
$this->currentKey = NULL;
}
else {
$return = TRUE;
}
$this->numProcessed++;
return $return;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* @file
* Info on migration team members. Display-only at the moment, but eventually
* there should be notification features.
*/
class MigrateTeamMember {
protected $name;
public function getName() {
return $this->name;
}
protected $emailAddress;
public function getEmailAddress() {
return $this->emailAddress;
}
protected $group;
public function getGroup() {
return $this->group;
}
public function __construct($name, $email_address, $group) {
$this->name = $name;
$this->emailAddress = $email_address;
$this->group = $group;
}
public function contact($subject, $text) {
// TODO
}
}