123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- <?php
- /**
- * @file
- * API and drush commands to support migration of data from external sources
- * into a Drupal installation.
- */
- // TODO:
- // Continue hook_schema_alter() for map & message tables?
- // Views hooks for map/message tables
- // xlat support?
- // Documentation
- // Tests
- define('MIGRATE_API_VERSION', 2);
- define('MIGRATE_ACCESS_BASIC', 'migration information');
- define('MIGRATE_ACCESS_ADVANCED', 'advanced migration information');
- /**
- * Retrieve a list of all active migrations, ordered by dependencies. To be
- * recognized, a class must be non-abstract, and derived from MigrationBase.
- *
- * @param $reset
- * If TRUE, the static cache of migrations will be flushed before attempting to
- * reinstantiate all active migrations. This can be important for script runs
- * where migration classes may be dynamically registered.
- *
- * @return
- * Array of migration objects, keyed by the machine name.
- */
- function migrate_migrations($reset = NULL) {
- static $migrations = array();
- if (!empty($migrations) && empty($reset)) {
- return $migrations;
- }
- // Get list of modules implementing Migrate API - mainly, we're looking to
- // make sure any dynamic migrations defined in hook_migrate_api() get registered.
- migrate_get_module_apis(TRUE);
- $dependencies_list = array();
- $dependent_migrations = array();
- $required_migrations = array();
- $result = db_select('migrate_status', 'ms')
- ->fields('ms', array('machine_name', 'class_name'))
- ->execute();
- foreach ($result as $row) {
- if (class_exists($row->class_name)) {
- $reflect = new ReflectionClass($row->class_name);
- if (!$reflect->isAbstract() && $reflect->isSubclassOf('MigrationBase')) {
- $migration = MigrationBase::getInstance($row->machine_name);
- if ($migration) {
- $dependencies = $migration->getDependencies();
- $dependencies_list[$row->machine_name] = $dependencies;
- if (count($dependencies) > 0) {
- // Set classes with dependencies aside for reordering
- $dependent_migrations[$row->machine_name] = $migration;
- $required_migrations += $dependencies;
- }
- else {
- // No dependencies, just add
- $migrations[$row->machine_name] = $migration;
- }
- }
- }
- else {
- MigrationBase::displayMessage(t('Class !class is no longer a valid concrete migration class',
- array('!class' => $row->class_name)));
- }
- }
- else {
- MigrationBase::displayMessage(t('Class !class no longer exists',
- array('!class' => $row->class_name)));
- }
- }
- $ordered_migrations = migrate_order_dependencies($dependencies_list);
- foreach ($ordered_migrations as $name) {
- if (!isset($migrations[$name])) {
- $migrations[$name] = $dependent_migrations[$name];
- }
- }
- // The migrations are now ordered according to their own dependencies - now order
- // them by group
- $groups = MigrateGroup::groups();
- // Seed the final list by properly-ordered groups.
- $final_migrations = array();
- foreach ($groups as $name => $group) {
- $final_migrations[$name] = array();
- }
- // Fill in the grouped list.
- foreach ($migrations as $machine_name => $migration) {
- if (!method_exists($migration, 'getGroup')) {
- MigrationBase::displayMessage(t('Migration !machine_name is not a valid Migration dependency.', array(
- '!machine_name' => $machine_name,
- )));
- }
- else {
- $final_migrations[$migration->getGroup()->getName()][$machine_name] = $migration;
- }
- }
- // Flatten the grouped list.
- $migrations = array();
- foreach ($final_migrations as $group_name => $group_migrations) {
- foreach ($group_migrations as $machine_name => $migration) {
- $migrations[$machine_name] = $migration;
- }
- }
- return $migrations;
- }
- /**
- * Invoke any available handlers attached to a given destination type.
- * If any handlers have dependencies defined, they will be invoked after
- * the specified handlers.
- *
- * @param $destination
- * Destination type ('Node', 'User', etc.) - generally the same string as
- * the destination class name without the MigrateDestination prefix.
- * @param $method
- * Method name such as 'prepare' (called at the beginning of an import operation)
- * or 'complete' (called at the end of an import operation).
- * @param ...
- * Parameters to be passed to the handler.
- */
- function migrate_handler_invoke_all($destination, $method) {
- $args = func_get_args();
- array_shift($args);
- array_shift($args);
- $return = array();
- $class_list = _migrate_class_list('MigrateDestinationHandler');
- $disabled = unserialize(variable_get('migrate_disabled_handlers', serialize(array())));
- foreach ($class_list as $class_name => $handler) {
- if (!in_array($class_name, $disabled) && $handler->handlesType($destination)
- && method_exists($handler, $method)) {
- migrate_instrument_start($class_name . '->' . $method);
- $result = call_user_func_array(array($handler, $method), $args);
- migrate_instrument_stop($class_name . '->' . $method);
- if (isset($result) && is_array($result)) {
- $return = array_merge($return, $result);
- }
- elseif (isset($result)) {
- $return[] = $result;
- }
- }
- }
- return $return;
- }
- /**
- * Invoke any available handlers attached to a given field type.
- *
- * @param $entity
- * The object we are building up before calling example_save().
- * @param $field_info
- * Array of info on the field, from field_info_field().
- * @param $instance
- * Array of info in the field instance, from field_info_instances().
- * @param $values
- * Array of incoming values, to be transformed into the appropriate structure
- * for the field type.
- * @param $method
- * Handler method to call (defaults to prepare()).
- */
- function migrate_field_handler_invoke_all($entity, array $field_info,
- array $instance, array $values, $method = 'prepare') {
- $return = array();
- $type = $field_info['type'];
- $class_list = _migrate_class_list('MigrateFieldHandler');
- $disabled = unserialize(variable_get('migrate_disabled_handlers',
- serialize(array())));
- $handler_called = FALSE;
- foreach ($class_list as $class_name => $handler) {
- if (!in_array($class_name, $disabled) && $handler->handlesType($type)
- && method_exists($handler, $method)) {
- migrate_instrument_start($class_name . '->' . $method);
- $result = call_user_func_array(array($handler, $method),
- array($entity, $field_info, $instance, $values));
- $handler_called = TRUE;
- migrate_instrument_stop($class_name . '->' . $method);
- if (isset($result) && is_array($result)) {
- $return = array_merge_recursive($return, $result);
- }
- elseif (isset($result)) {
- $return[] = $result;
- }
- }
- }
- if (!$handler_called && $method == 'prepare') {
- $handler = new MigrateDefaultFieldHandler();
- migrate_instrument_start('MigrateDefaultFieldHandler->prepare');
- $result = call_user_func_array(array($handler, 'prepare'),
- array($entity, $field_info, $instance, $values));
- migrate_instrument_stop('MigrateDefaultFieldHandler->prepare');
- if (isset($result) && is_array($result)) {
- $return = array_merge_recursive($return, $result);
- }
- elseif (isset($result)) {
- $return[] = $result;
- }
- }
- return $return;
- }
- /**
- * For a given parent class, identify and instantiate objects for any non-abstract
- * classes derived from the parent, returning an array of the objects indexed by
- * class name. The array will be ordered such that any classes with dependencies
- * are listed after the classes they are dependent on.
- *
- * @param $parent_class
- * Name of a class from which results will be derived.
- * @return
- * Array of objects, keyed by the class name.
- */
- function _migrate_class_list($parent_class) {
- // Get info on modules implementing Migrate API
- static $module_info;
- if (!isset($module_info)) {
- $module_info = migrate_get_module_apis();
- }
- static $class_lists = array();
- if (!isset($class_lists[$parent_class])) {
- $class_lists[$parent_class] = array();
- if ($parent_class == 'MigrateDestinationHandler') {
- $handler_key = 'destination handlers';
- }
- else {
- $handler_key = 'field handlers';
- }
- // Add explicitly-registered handler classes
- foreach ($module_info as $info) {
- if (isset($info[$handler_key]) && is_array($info[$handler_key])) {
- foreach ($info[$handler_key] as $handler_class) {
- $class_lists[$parent_class][$handler_class] = new $handler_class();
- }
- }
- }
- }
- return $class_lists[$parent_class];
- }
- /**
- * Implements hook_hook_info().
- */
- function migrate_hook_info() {
- // Look for hook_migrate_api() in example.migrate.inc.
- $hooks['migrate_api'] = array(
- 'group' => 'migrate',
- );
- $hooks['migrate_api_alter'] = array(
- 'group' => 'migrate',
- );
- return $hooks;
- }
- /**
- * Implementation of hook_permission().
- */
- function migrate_permission() {
- return array(
- MIGRATE_ACCESS_BASIC => array(
- 'title' => t('Access to basic migration information'),
- ),
- MIGRATE_ACCESS_ADVANCED => array(
- 'title' => t('Access to advanced migration information'),
- ),
- );
- }
- /**
- * Get a list of modules that support the current migrate API.
- */
- function migrate_get_module_apis($reset = FALSE) {
- static $cache = NULL;
- if ($reset) {
- $cache = NULL;
- }
- if (!isset($cache)) {
- $cache = array();
- foreach (module_implements('migrate_api') as $module) {
- $function = $module . '_migrate_api';
- $info = $function();
- if (isset($info['api']) && $info['api'] == MIGRATE_API_VERSION) {
- $cache[$module] = $info;
- }
- else {
- drupal_set_message(t('%function supports Migrate API version %modversion,
- Migrate module API version is %version - migration support not loaded.',
- array('%function' => $function, '%modversion' => $info['api'],
- '%version' => MIGRATE_API_VERSION)));
- }
- }
- // Allow modules to alter the migration information.
- drupal_alter('migrate_api', $cache);
- }
- return $cache;
- }
- /**
- * Register any migrations defined in hook_migrate_api().
- *
- * @param array $machine_names
- * If populated, only (re)register the specified migrations.
- */
- function migrate_static_registration($machine_names = array()) {
- $module_info = migrate_get_module_apis(TRUE);
- foreach ($module_info as $module => $info) {
- // Register any groups defined via the hook.
- if (isset($info['groups']) && is_array($info['groups'])) {
- foreach ($info['groups'] as $name => $arguments) {
- $title = $arguments['title'];
- unset($arguments['title']);
- MigrateGroup::register($name, $title, $arguments);
- }
- }
- // Register any migrations defined via the hook.
- if (isset($info['migrations']) && is_array($info['migrations'])) {
- foreach ($info['migrations'] as $machine_name => $arguments) {
- // If we have an explicit list to register, skip any not in the list.
- if (!empty($machine_names) && !in_array($machine_name, $machine_names)) {
- continue;
- }
- $class_name = $arguments['class_name'];
- unset($arguments['class_name']);
- // Call the right registerMigration implementation. Note that this means
- // that classes that override registerMigration() must always call it
- // directly, they cannot register those classes by defining them in
- // hook_migrate_api() and expect their extension to be called.
- if (is_subclass_of($class_name, 'Migration')) {
- Migration::registerMigration($class_name, $machine_name, $arguments);
- }
- else {
- MigrationBase::registerMigration($class_name, $machine_name, $arguments);
- }
- }
- }
- }
- }
- /**
- * Do a topological sort on our dependencies graph.
- */
- function migrate_order_dependencies($dependencies) {
- $visited = array();
- $list = array();
- foreach (array_keys($dependencies) as $name) {
- $visited[$name] = FALSE;
- }
- foreach (array_keys($dependencies) as $name) {
- migrate_visit_dependent($dependencies, $name, $list, $visited);
- }
- return $list;
- }
- /**
- * Depth-first search for independent migrations.
- */
- function migrate_visit_dependent($dependencies, $name, &$list, &$visited) {
- if ($visited[$name]) {
- if ($list[$name]) {
- return;
- }
- else {
- throw new MigrateException(t('Failure to sort migration list due to circular dependencies involving %name.', array('%name' => $name)));
- }
- }
- $visited[$name] = TRUE;
- if (isset($dependencies[$name])) {
- foreach ($dependencies[$name] as $dependent) {
- migrate_visit_dependent($dependencies, $dependent, $list, $visited);
- }
- }
- $list[$name] = $name;
- }
- /**
- * Implements hook_watchdog().
- * Find the migration that is currently running and notify it.
- *
- * @param array $log_entry
- */
- function migrate_watchdog($log_entry) {
- // Ensure that the Migration class exists, as different bootstrap phases may
- // not have included migration.inc yet.
- if (class_exists('Migration') && $migration = Migration::currentMigration()) {
- switch ($log_entry['severity']) {
- case WATCHDOG_EMERGENCY:
- case WATCHDOG_ALERT:
- case WATCHDOG_CRITICAL:
- case WATCHDOG_ERROR:
- $severity = MigrationBase::MESSAGE_ERROR;
- break;
- case WATCHDOG_WARNING:
- $severity = MigrationBase::MESSAGE_WARNING;
- break;
- case WATCHDOG_NOTICE:
- $severity = MigrationBase::MESSAGE_NOTICE;
- break;
- case WATCHDOG_DEBUG:
- case WATCHDOG_INFO:
- default:
- $severity = MigrationBase::MESSAGE_INFORMATIONAL;
- break;
- }
- $variables = is_array($log_entry['variables']) ? $log_entry['variables'] : array();
- $migration->saveMessage(t($log_entry['message'], $variables), $severity);
- }
- }
- /**
- * Resource functions modeled on Drupal's timer functions
- */
- /**
- * Save memory usage with the specified name. If you start and stop the same
- * memory name multiple times, the measured differences will be accumulated.
- *
- * @param name
- * The name of the memory measurement.
- */
- function migrate_memory_start($name) {
- global $_migrate_memory;
- $_migrate_memory[$name]['start'] = memory_get_usage();
- $_migrate_memory[$name]['count'] =
- isset($_migrate_memory[$name]['count']) ? ++$_migrate_memory[$name]['count'] : 1;
- }
- /**
- * Read the current memory value without recording the change.
- *
- * @param name
- * The name of the memory measurement.
- * @return
- * The change in bytes since the last start.
- */
- function migrate_memory_read($name) {
- global $_migrate_memory;
- if (isset($_migrate_memory[$name]['start'])) {
- $stop = memory_get_usage();
- $diff = $stop - $_migrate_memory[$name]['start'];
- if (isset($_migrate_memory[$name]['bytes'])) {
- $diff += $_migrate_memory[$name]['bytes'];
- }
- return $diff;
- }
- return $_migrate_memory[$name]['bytes'];
- }
- /**
- * Stop the memory counter with the specified name.
- *
- * @param name
- * The name of the memory measurement.
- * @return
- * A memory array. The array contains the number of times the memory has been
- * started and stopped (count) and the accumulated memory difference value in bytes.
- */
- function migrate_memory_stop($name) {
- global $_migrate_memory;
- if (isset($_migrate_memory[$name])) {
- if (isset($_migrate_memory[$name]['start'])) {
- $stop = memory_get_usage();
- $diff = $stop - $_migrate_memory[$name]['start'];
- if (isset($_migrate_memory[$name]['bytes'])) {
- $_migrate_memory[$name]['bytes'] += $diff;
- }
- else {
- $_migrate_memory[$name]['bytes'] = $diff;
- }
- unset($_migrate_memory[$name]['start']);
- }
- return $_migrate_memory[$name];
- }
- }
- /**
- * Start measuring time and (optionally) memory consumption over a section of code.
- * Note that the memory consumption measurement is generally not useful in
- * lower areas of the code, where data is being generated that will be freed
- * by the next call to the same area. For example, measuring the memory
- * consumption of db_query is not going to be helpful.
- *
- * @param $name
- * The name of the measurement.
- * @param $include_memory
- * Measure both memory and timers. Defaults to FALSE (timers only).
- */
- function migrate_instrument_start($name, $include_memory = FALSE) {
- global $_migrate_track_memory, $_migrate_track_timer;
- if ($_migrate_track_memory && $include_memory) {
- migrate_memory_start($name);
- }
- if ($_migrate_track_timer) {
- timer_start($name);
- }
- }
- /**
- * Stop measuring both memory and time consumption over a section of code.
- *
- * @param $name
- * The name of the measurement.
- */
- function migrate_instrument_stop($name) {
- global $_migrate_track_memory, $_migrate_track_timer;
- if ($_migrate_track_timer) {
- timer_stop($name);
- }
- if ($_migrate_track_memory) {
- migrate_memory_stop($name);
- }
- }
- /**
- * Call hook_migrate_overview for overall documentation on implemented migrations.
- */
- function migrate_overview() {
- $overview = '';
- $results = module_invoke_all('migrate_overview');
- foreach ($results as $result) {
- $overview .= $result . ' ';
- }
- return $overview;
- }
- /**
- * Implements hook_modules_enabled.
- */
- function migrate_modules_enabled($modules) {
- if (array_intersect($modules, module_implements('migrate_api'))) {
- migrate_static_registration();
- }
- }
- /**
- * Implements hook_module_implements_alter().
- */
- function migrate_module_implements_alter(&$implementation, $hook) {
- // Ensure that the Migration class exists, as different bootstrap phases may
- // not have included migration.inc yet.
- if (class_exists('Migration') && $migration = Migration::currentMigration()) {
- $disable_hooks = $migration->getDisableHooks();
- if (isset($disable_hooks[$hook])) {
- foreach ($disable_hooks[$hook] as $module) {
- unset($implementation[$module]);
- }
- }
- }
- }
|