[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/migrate/ -> migrate.module (source)

   1  <?php
   2  // $Id: migrate.module,v 1.1.2.62 2009/09/19 16:23:57 mikeryan Exp $
   3  
   4  define('MIGRATE_ACCESS_BASIC', 'basic migration tools');
   5  define('MIGRATE_ACCESS_ADVANCED', 'advanced migration tools');
   6  
   7  define('MIGRATE_MESSAGE_ERROR', 1);
   8  define('MIGRATE_MESSAGE_WARNING', 2);
   9  define('MIGRATE_MESSAGE_NOTICE', 3);
  10  define('MIGRATE_MESSAGE_INFORMATIONAL', 4);
  11  
  12  define('MIGRATE_STATUS_SUCCESS', 1);
  13  define('MIGRATE_STATUS_FAILURE', 2);
  14  define('MIGRATE_STATUS_TIMEDOUT', 3);
  15  define('MIGRATE_STATUS_CANCELLED', 4);
  16  define('MIGRATE_STATUS_IN_PROGRESS', 5);
  17  
  18  /**
  19   * @file
  20   * This module provides tools at "administer >> content >> migrate"
  21   * for analyzing data from various sources and importing them into Drupal tables.
  22   */
  23  
  24  /**
  25   * Call a migrate hook
  26   */
  27  function migrate_invoke_all($hook) {
  28    // Let modules do any one-time initialization (e.g., including migration support files)
  29    module_invoke_all('migrate_init');
  30    $args = func_get_args();
  31    $hookfunc = "migrate" . "_$hook";
  32    unset($args[0]);
  33    $return = array();
  34    $modulelist = module_implements($hookfunc);
  35    foreach ($modulelist as $module) {
  36      $function = $module . '_' . $hookfunc;
  37      $result = call_user_func_array($function, $args);
  38      if (isset($result) && is_array($result)) {
  39        $return = array_merge_recursive($return, $result);
  40      }
  41      elseif (isset($result)) {
  42        $return[] = $result;
  43      }
  44    }
  45    return $return;
  46  }
  47  
  48  /**
  49   * Call a destination hook (e.g., hook_migrate_prepare_node). Use this version
  50   * for hooks with the precise signature below, so that the object can be passed by
  51   * reference.
  52   *
  53   * @param $hook
  54   * @param $object
  55   * @param $tblinfo
  56   * @param $row
  57   * @return
  58   */
  59  function migrate_destination_invoke_all($hook, &$object, $tblinfo, $row) {
  60    // We could have used module_invoke_all, but unfortunately
  61    // module_invoke_all passes all arguments by value.
  62    $errors = array();
  63    $hook = 'migrate_' . $hook;
  64    foreach (module_implements($hook) as $module_name) {
  65      $function = $module_name . '_' . $hook;
  66      if (function_exists($function)) {
  67        timer_start($function);
  68        $errors = array_merge($errors, (array)$function($object, $tblinfo, $row));
  69        timer_stop($function);
  70      }
  71    }
  72    return $errors;
  73  }
  74  
  75  /**
  76   * Save a new or updated content set
  77   *
  78   * @param $content_set
  79   *  An array or object representing the content set. This is passed by reference (so
  80   *  when adding a new content set the ID can be set)
  81   * @param $options
  82   *  Array of additional options for saving the content set. Currently:
  83   *    base_table: The base table of the view - if provided, we don't need
  84   *                to load the view.
  85   *    base_database: The database of the base table - if base_table is present
  86   *                and base_database omitted, it defaults to 'default'
  87   * @return
  88   *  The ID of the content set that was saved, or NULL if nothing was saved
  89   */
  90  function migrate_save_content_set(&$content_set, $options = array()) {
  91    // Deal with objects internally (but remember if we need to put the parameter
  92    // back to an array)
  93    if (is_array($content_set)) {
  94      $was_array = TRUE;
  95      $content_set = (object) $content_set;
  96    }
  97    else {
  98      $was_array = FALSE;
  99    }
 100  
 101    // Update or insert the content set record as appropriate
 102    if (isset($content_set->mcsid)) {
 103      drupal_write_record('migrate_content_sets', $content_set, 'mcsid');
 104    }
 105    else {
 106      drupal_write_record('migrate_content_sets', $content_set);
 107    }
 108  
 109    // Create or modify map and message tables
 110    $maptablename = migrate_map_table_name($content_set->mcsid);
 111    $msgtablename = migrate_message_table_name($content_set->mcsid);
 112  
 113    // TODO: For now, PK must be in base_table
 114  
 115    // If the caller tells us the base table of the view, we don't need
 116    // to load the view (which would not work when called from hook_install())
 117    if (isset($options['base_table'])) {
 118      $tablename = $options['base_table'];
 119      if (isset($options['base_database'])) {
 120        $tabledb = $options['base_database'];
 121      }
 122      else {
 123        $tabledb = 'default';
 124      }
 125    }
 126    else {
 127      // Get the proper field definition for the sourcekey
 128      $view = views_get_view($content_set->view_name);
 129      if (!$view) {
 130        drupal_set_message(t('View !view does not exist - either (re)create this view, or
 131          remove the content set using it.', array('!view' => $content_set->view_name)));
 132        return NULL;
 133      }
 134      // Must do this to load the database
 135      $view->init_query();
 136  
 137      if (isset($view->base_database)) {
 138        $tabledb = $view->base_database;
 139      }
 140      else {
 141        $tabledb = 'default';
 142      }
 143      $tablename = $view->base_table;
 144    }
 145  
 146    db_set_active($tabledb);
 147    $inspect = schema_invoke('inspect');
 148    db_set_active('default');
 149    $sourceschema = $inspect[$tablename];
 150    // If the PK of the content set is defined, make sure we have a mapping table
 151    if (isset($content_set->sourcekey) && $content_set->sourcekey) {
 152      $sourcefield = $sourceschema['fields'][$content_set->sourcekey];
 153      // The field name might be <table>_<column>...
 154      if (!$sourcefield) {
 155        $sourcekey = drupal_substr($content_set->sourcekey, drupal_strlen($tablename) + 1);
 156        $sourcefield = $sourceschema['fields'][$sourcekey];
 157      }
 158      // But - we don't want serial fields to behave serially, so change to int
 159      if ($sourcefield['type'] == 'serial') {
 160        $sourcefield['type'] = 'int';
 161      }
 162  
 163      $schema_change = FALSE;
 164  
 165      if (!db_table_exists($maptablename)) {
 166        $schema = _migrate_map_table_schema($sourcefield);
 167        db_create_table($ret, $maptablename, $schema);
 168        // Expose map table to views
 169        tw_add_tables(array($maptablename));
 170        tw_add_fk($maptablename, 'destid');
 171  
 172        $schema = _migrate_message_table_schema($sourcefield);
 173        db_create_table($ret, $msgtablename, $schema);
 174        // Expose messages table to views
 175        tw_add_tables(array($msgtablename));
 176        tw_add_fk($msgtablename, 'sourceid');
 177        $schema_change = TRUE;
 178      }
 179      else {
 180        // TODO: Deal with varchar->int case where there is existing non-int data
 181        $desired_schema = _migrate_map_table_schema($sourcefield);
 182        $actual_schema = $inspect[$maptablename];
 183        if ($desired_schema['fields']['sourceid'] != $actual_schema['fields']['sourceid']) {
 184          $ret = array();
 185          db_drop_primary_key($ret, $maptablename);
 186          db_change_field($ret, $maptablename, 'sourceid', 'sourceid',
 187            $sourcefield, array('primary key' => array('sourceid')));
 188          tw_perform_analysis($maptablename);
 189          $schema_change = TRUE;
 190        }
 191        $desired_schema = _migrate_message_table_schema($sourcefield);
 192        $actual_schema = $inspect[$msgtablename];
 193        if ($desired_schema['fields']['sourceid'] != $actual_schema['fields']['sourceid']) {
 194          $ret = array();
 195          db_drop_index($ret, $msgtablename, 'sourceid');
 196          db_change_field($ret, $msgtablename, 'sourceid', 'sourceid',
 197            $sourcefield, array('indexes' => array('sourceid' => array('sourceid'))));
 198          tw_perform_analysis($maptablename);
 199          $schema_change = TRUE;
 200        }
 201      }
 202      // Make sure the schema gets updated to reflect changes
 203      if ($schema_change) {
 204        cache_clear_all('schema', 'cache');
 205      }
 206    }
 207  
 208    if ($was_array) {
 209      $content_set = (array)$content_set;
 210      return $content_set['mcsid'];
 211    }
 212    else {
 213      return $content_set->mcsid;
 214    }
 215  }
 216  
 217  function migrate_save_content_mapping(&$mapping) {
 218    if ($mapping->mcmid) {
 219      drupal_write_record('migrate_content_mappings', $mapping, 'mcmid');
 220    }
 221    else {
 222      drupal_write_record('migrate_content_mappings', $mapping);
 223    }
 224    return $mapping->mcmid;
 225  }
 226  
 227  function migrate_delete_content_set($mcsid) {
 228    // First, remove the map and message tables from the Table Wizard, and drop them
 229    $ret = array();
 230    $maptable = migrate_map_table_name($mcsid);
 231    $msgtable = migrate_message_table_name($mcsid);
 232    if (db_table_exists($maptable)) {
 233      tw_remove_tables(array($maptable, $msgtable));
 234      db_drop_table($ret, $maptable);
 235      db_drop_table($ret, $msgtable);
 236    }
 237  
 238    // Then, delete the content set data
 239    $sql = "DELETE FROM {migrate_content_mappings} WHERE mcsid=%d";
 240    db_query($sql, $mcsid);
 241    $sql = "DELETE FROM {migrate_content_sets} WHERE mcsid=%d";
 242    db_query($sql, $mcsid);
 243  }
 244  
 245  function migrate_delete_content_mapping($mcmid) {
 246    $sql = "DELETE FROM {migrate_content_mappings} WHERE mcmid=%d";
 247    db_query($sql, $mcmid);
 248  }
 249  
 250  /**
 251   * Convenience function for generating a message array
 252   *
 253   * @param $message
 254   *  Text describing the error condition
 255   * @param $type
 256   *  One of the MIGRATE_MESSAGE constants, identifying the level of error
 257   * @return
 258   *  Structured array suitable for return from an import hook
 259   */
 260  function migrate_message($message, $type = MIGRATE_MESSAGE_ERROR) {
 261    $error = array(
 262      'level' => $type,
 263      'message' => $message,
 264    );
 265    return $error;
 266  }
 267  
 268  /**
 269   * Add a mapping from source ID to destination ID for the specified content set
 270   *
 271   * @param $mcsid
 272   *  ID of the content set being processed
 273   * @param $sourceid
 274   *  Primary key value from the source
 275   * @param $destid
 276   *  Primary key value from the destination
 277   */
 278  function migrate_add_mapping($mcsid, $sourceid, $destid) {
 279    static $maptables = array();
 280    if (!isset($maptables[$mcsid])) {
 281      $maptables[$mcsid] = migrate_map_table_name($mcsid);
 282    }
 283    $mapping = new stdClass;
 284    $mapping->sourceid = $sourceid;
 285    $mapping->destid = $destid;
 286    drupal_write_record($maptables[$mcsid], $mapping);
 287  }
 288  
 289  /**
 290   * Clear migrated objects from the specified content set
 291   *
 292   * @param $mcsid
 293   *  ID of the content set to clear
 294   * @param $messages
 295   *  Array of messages to (ultimately) be displayed by the caller.
 296   * @param $options
 297   *  Keyed array of optional options:
 298   *    itemlimit - Maximum number of items to process
 299   *    timelimit - Unix timestamp after which to stop processing
 300   *    idlist - Comma-separated list of source IDs to process, instead of proceeding through
 301   *      all unmigrated rows
 302   *    feedback - Keyed array controlling status feedback to the caller
 303   *      function - PHP function to call, passing a message to be displayed
 304   *      frequency - How often to call the function
 305   *      frequency_unit - How to interpret frequency (items or seconds)
 306   *
 307   * @return
 308   *  Status of the migration process:
 309   */
 310  function migrate_content_process_clear($mcsid, &$messages = array(), &$options = array()) {
 311    $itemlimit = $options['itemlimit'];
 312    $timelimit = $options['timelimit'];
 313    $idlist = $options['idlist'];
 314    $lastfeedback = time();
 315    if (isset($options['feedback'])) {
 316      $feedback = $options['feedback']['function'];
 317      $frequency = $options['feedback']['frequency'];
 318      $frequency_unit = $options['feedback']['frequency_unit'];
 319    }
 320  
 321    $result = db_query("SELECT *
 322                        FROM {migrate_content_sets}
 323                        WHERE mcsid=%d", $mcsid);
 324    $tblinfo = db_fetch_object($result);
 325    $description = $tblinfo->description;
 326    if ($tblinfo->semaphore) {
 327      $messages[] = t('Content set !content_set has an operation already in progress.',
 328        array('!content_set' => $description));
 329      return MIGRATE_STATUS_IN_PROGRESS;
 330    }
 331    else {
 332      $tblinfo->semaphore = TRUE;
 333      drupal_write_record('migrate_content_sets', $tblinfo, 'mcsid');
 334    }
 335    $desttype = $tblinfo->desttype;
 336    $view_name = $tblinfo->view_name;
 337    $contenttype = $tblinfo->contenttype;
 338    $sourcekey = $tblinfo->sourcekey;
 339  
 340    $maptable = migrate_map_table_name($mcsid);
 341    $msgtablename = migrate_message_table_name($mcsid);
 342    $processstart = microtime(TRUE);
 343    $status = MIGRATE_STATUS_IN_PROGRESS;
 344  
 345    // If we're being called on a content set that isn't flagged for clearing, temporarily flag it
 346    $original_clearing = $tblinfo->clearing;
 347    if (!$original_clearing) {
 348      $sql = "UPDATE {migrate_content_sets} SET clearing=1 WHERE mcsid=%d";
 349      db_query($sql, $mcsid);
 350    }
 351  
 352    $deleted = 0;
 353    if ($idlist) {
 354      $sql = "SELECT sourceid,destid FROM {" . $maptable . "} WHERE sourceid IN ($idlist)";
 355    }
 356    else {
 357      $sql = "SELECT sourceid,destid FROM {" . $maptable . "}";
 358    }
 359  
 360    timer_start('delete query');
 361    if ($itemlimit) {
 362      $deletelist = db_query_range($sql, 0, $itemlimit);
 363    }
 364    else {
 365      $deletelist = db_query($sql);
 366    }
 367    timer_stop('delete query');
 368  
 369    while ($row = db_fetch_object($deletelist)) {
 370      // Recheck clearing flag - permits dynamic interruption of jobs
 371      $sql = "SELECT clearing FROM {migrate_content_sets} WHERE mcsid=%d";
 372      $clearing = db_result(db_query($sql, $mcsid));
 373      if (!$clearing) {
 374        $status = MIGRATE_STATUS_CANCELLED;
 375        break;
 376      }
 377      // Check for time out if there is time info present
 378      if (isset($timelimit) && time() >= $timelimit) {
 379        $status = MIGRATE_STATUS_TIMEDOUT;
 380        break;
 381      }
 382      if (isset($feedback)) {
 383        if (($frequency_unit == 'seconds' && time()-$lastfeedback >= $frequency) ||
 384            ($frequency_unit == 'items' && $deleted >= $frequency)) {
 385          $message = _migrate_progress_message($lastfeedback, $deleted, $description, FALSE, $status);
 386          $feedback($message);
 387          $lastfeedback = time();
 388          $deleted = 0;
 389        }
 390      }
 391      // @TODO: Should return success/failure. Problem: node_delete doesn't return anything...
 392      migrate_invoke_all("delete_$contenttype", $row->destid);
 393      timer_start('clear map/msg');
 394      db_query("DELETE FROM {" . $maptable . "} WHERE sourceid=%d", $row->sourceid);
 395      db_query("DELETE FROM {" . $msgtablename . "} WHERE sourceid=%d AND level=%d",
 396        $row->sourceid, MIGRATE_MESSAGE_INFORMATIONAL);
 397      timer_stop('clear map/msg');
 398      $deleted++;
 399    }
 400  
 401    if ($status == MIGRATE_STATUS_IN_PROGRESS) {
 402      $status = MIGRATE_STATUS_SUCCESS;
 403    }
 404  
 405    $message = _migrate_progress_message($lastfeedback, $deleted, $description, FALSE, $status);
 406    if ($status == MIGRATE_STATUS_SUCCESS) {
 407      // Mark that we're done
 408      $tblinfo->clearing = 0;
 409      drupal_write_record('migrate_content_sets', $tblinfo, 'mcsid');
 410      // Remove old messages before beginning new import process
 411      db_query("DELETE FROM {" . $msgtablename . "} WHERE level <> %d", MIGRATE_MESSAGE_INFORMATIONAL);
 412    }
 413    if (isset($feedback)) {
 414      $feedback($message);
 415    }
 416    else {
 417      $messages[] = $message;
 418    }
 419    watchdog('migrate', $message);
 420    if (!$original_clearing) {
 421      $sql = "UPDATE {migrate_content_sets} SET clearing=0 WHERE mcsid=%d";
 422      db_query($sql, $mcsid);
 423    }
 424  
 425    $tblinfo->semaphore = FALSE;
 426    drupal_write_record('migrate_content_sets', $tblinfo, 'mcsid');
 427  
 428    return $status;
 429  }
 430  
 431  /**
 432   * Import objects from the specified content set
 433   *
 434   * @param $mcsid
 435   *  ID of the content set to clear
 436   * @param $messages
 437   *  Array of messages to (ultimately) be displayed by the caller.
 438   * @param $options
 439   *  Keyed array of optional options:
 440   *    itemlimit - Maximum number of items to process
 441   *    timelimit - Unix timestamp after which to stop processing
 442   *    idlist - Comma-separated list of source IDs to process, instead of proceeding through
 443   *      all unmigrated rows
 444   *    feedback - Keyed array controlling status feedback to the caller
 445   *      function - PHP function to call, passing a message to be displayed
 446   *      frequency - How often to call the function
 447   *      frequency_unit - How to interpret frequency (items or seconds)
 448   *
 449   * @return
 450   *  Status of the migration process:
 451   */
 452  function migrate_content_process_import($mcsid,  &$messages = array(), &$options = array()) {
 453    $itemlimit = $options['itemlimit'];
 454    $timelimit = $options['timelimit'];
 455    $idlist = $options['idlist'];
 456    $lastfeedback = time();
 457    if (isset($options['feedback'])) {
 458      $feedback = $options['feedback']['function'];
 459      $frequency = $options['feedback']['frequency'];
 460      $frequency_unit = $options['feedback']['frequency_unit'];
 461    }
 462    $result = db_query("SELECT *
 463                        FROM {migrate_content_sets}
 464                        WHERE mcsid=%d", $mcsid);
 465    $tblinfo = db_fetch_object($result);
 466    $description = $tblinfo->description;
 467    if ($tblinfo->semaphore) {
 468      $messages[] = t('Content set !content_set has an operation already in progress.',
 469        array('!content_set' => $description));
 470      return MIGRATE_STATUS_IN_PROGRESS;
 471    }
 472    else {
 473      $tblinfo->semaphore = TRUE;
 474      drupal_write_record('migrate_content_sets', $tblinfo, 'mcsid');
 475    }
 476    $desttype = $tblinfo->desttype;
 477    $view_name = $tblinfo->view_name;
 478    $contenttype = $tblinfo->contenttype;
 479    $sourcekey = $tblinfo->sourcekey;
 480  
 481    $maptable = migrate_map_table_name($mcsid);
 482    $msgtablename = migrate_message_table_name($mcsid);
 483    $processstart = microtime(TRUE);
 484    $status = MIGRATE_STATUS_IN_PROGRESS;
 485  
 486    // If we're being called on a content set that isn't flagged for importing, temporarily flag it
 487    $original_importing = $tblinfo->importing || $tblinfo->scanning;
 488    if (!$original_importing) {
 489      $sql = "UPDATE {migrate_content_sets} SET importing=1 WHERE mcsid=%d";
 490      db_query($sql, $mcsid);
 491    }
 492  
 493    $collist = db_query("SELECT srcfield, destfield, default_value
 494                         FROM {migrate_content_mappings}
 495                         WHERE mcsid=%d AND (srcfield <> '' OR default_value <> '')
 496                         ORDER BY mcmid",
 497                        $mcsid);
 498    $fields = array();
 499    while ($row = db_fetch_object($collist)) {
 500      $fields[$row->destfield]['srcfield'] = $row->srcfield;
 501      $fields[$row->destfield]['default_value'] = $row->default_value;
 502    }
 503    $tblinfo->fields = $fields;
 504    $tblinfo->maptable = $maptable;
 505    // We pick up everything in the input view that is not already imported, and
 506    // not already errored out
 507    // Emulate views execute(), so we can scroll through the results ourselves
 508    $view = views_get_view($view_name);
 509    if (!$view) {
 510      $messages[] = t('View !view does not exist - either (re)create this view, or
 511        remove the content set using it.', array('!view' => $view_name));
 512      return MIGRATE_STATUS_FAILURE;
 513    }
 514    $view->build();
 515  
 516    // Let modules modify the view just prior to executing it.
 517    foreach (module_implements('views_pre_execute') as $module) {
 518      $function = $module . '_views_pre_execute';
 519      $function($view);
 520    }
 521  
 522    $viewdb = $view->base_database;
 523  
 524    // Add a left join to the map table, and only include rows not in the map
 525    $join = new views_join;
 526  
 527    // Views prepends <base_table>_ to column names other than the base table's
 528    // primary key - we need to strip that here for the join to work. But, it's
 529    // common for tables to have the tablename beginning field names (e.g.,
 530    // table cms with PK cms_id). Deal with that as well...
 531    $baselen = drupal_strlen($view->base_table);
 532    if (!strncasecmp($sourcekey, $view->base_table . '_', $baselen + 1)) {
 533      // So, which case is it? Ask the schema module...
 534      db_set_active($viewdb);
 535      $inspect = schema_invoke('inspect', db_prefix_tables('{'. $view->base_table .'}'));
 536      db_set_active('default');
 537      $tableschema = $inspect[$view->base_table];
 538      $sourcefield = $tableschema['fields'][$sourcekey];
 539      if (!$sourcefield) {
 540        $joinkey = drupal_substr($sourcekey, $baselen + 1);
 541        $sourcefield = $tableschema['fields'][$joinkey];
 542        if (!$sourcefield) {
 543          $messages[] = t("In view !view, can't find key !key for table !table",
 544            array('!view' => $view_name, '!key' => $sourcekey, '!table' => $view->base_table));
 545          return MIGRATE_STATUS_FAILURE;
 546        }
 547      }
 548      else {
 549        $joinkey = $sourcekey;
 550      }
 551    }
 552    else {
 553      $joinkey = $sourcekey;
 554    }
 555    $join->construct($maptable, $view->base_table, $joinkey, 'sourceid');
 556    $view->query->add_relationship($maptable, $join, $view->base_table);
 557    $view->query->add_where(0, "$maptable.sourceid IS NULL", $view->base_table);
 558  
 559    // Ditto for the errors table
 560    $join = new views_join;
 561  
 562    $join->construct($msgtablename, $view->base_table, $joinkey, 'sourceid');
 563    $view->query->add_relationship($msgtablename, $join, $view->base_table);
 564    $view->query->add_where(0, "$msgtablename.sourceid IS NULL", $view->base_table);
 565  
 566    // If running over a selected list of IDs, pass those in to the query
 567    if ($idlist) {
 568      $view->query->add_where($view->options['group'], $view->base_table . ".$sourcekey IN ($idlist)",
 569        $view->base_table);
 570    }
 571  
 572    // We can't seem to get $view->build() to rebuild build_info, so go straight into the query object
 573    $query = $view->query->query();
 574  
 575    $query = db_rewrite_sql($query, $view->base_table, $view->base_field,
 576                            array('view' => &$view));
 577    $args = $view->build_info['query_args'];
 578    $replacements = module_invoke_all('views_query_substitutions', $view);
 579    $query = str_replace(array_keys($replacements), $replacements, $query);
 580    if (is_array($args)) {
 581      foreach ($args as $id => $arg) {
 582        $args[$id] = str_replace(array_keys($replacements), $replacements, $arg);
 583      }
 584    }
 585  
 586    // Now, make the current db name explicit if content set is pulling tables from another DB
 587    if ($viewdb <> 'default') {
 588      global $db_url;
 589      $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url);
 590      $currdb = drupal_substr($url['path'], 1);
 591      $query = str_replace('{' . $maptable . '}',
 592        $currdb . '.' . '{' . $maptable . '}', $query);
 593      $query = str_replace('{' . $msgtablename . '}',
 594        $currdb . '.' . '{' . $msgtablename . '}', $query);
 595      db_set_active($viewdb);
 596    }
 597  
 598    //drupal_set_message($query);
 599    timer_start('execute view query');
 600    if ($itemlimit) {
 601      $importlist = db_query_range($query, $args, 0, $itemlimit);
 602    }
 603    else {
 604      $importlist = db_query($query, $args);
 605    }
 606    timer_stop('execute view query');
 607  
 608    if ($viewdb != 'default') {
 609      db_set_active('default');
 610    }
 611  
 612    $imported = 0;
 613    timer_start('db_fetch_object');
 614    while ($row = db_fetch_object($importlist)) {
 615      timer_stop('db_fetch_object');
 616      // Recheck importing flag - permits dynamic interruption of cron jobs
 617      $sql = "SELECT importing,scanning FROM {migrate_content_sets} WHERE mcsid=%d";
 618      $checkrow = db_fetch_object(db_query($sql, $mcsid));
 619      $importing = $checkrow->importing;
 620      $scanning = $checkrow->scanning;
 621      if (!($importing || $scanning)) {
 622        $status = MIGRATE_STATUS_CANCELLED;
 623        break;
 624      }
 625  
 626      // Check for time out if there is time info present
 627      if (isset($timelimit) && time() >= $timelimit) {
 628        $status = MIGRATE_STATUS_TIMEDOUT;
 629        break;
 630      }
 631  
 632      if (isset($feedback)) {
 633        if (($frequency_unit == 'seconds' && time()-$lastfeedback >= $frequency) ||
 634            ($frequency_unit == 'items' && $imported >= $frequency)) {
 635          $message = _migrate_progress_message($lastfeedback, $imported, $description, TRUE, $status);
 636          $feedback($message);
 637          $lastfeedback = time();
 638          $imported = 0;
 639        }
 640      }
 641      timer_start('import hooks');
 642      $errors = migrate_invoke_all("import_$contenttype", $tblinfo, $row);
 643      timer_stop('import hooks');
 644  
 645      // Ok, we're done. Preview the node or save it (if no errors).
 646      if (count($errors)) {
 647        $success = TRUE;
 648        foreach ($errors as $error) {
 649          if (!isset($error['level'])) {
 650            $error['level'] = MIGRATE_MESSAGE_ERROR;
 651          }
 652          if ($error['level'] != MIGRATE_MESSAGE_INFORMATIONAL) {
 653            $success = FALSE;
 654          }
 655          db_query("INSERT INTO {" . $msgtablename . "}
 656                    (sourceid, level, message)
 657                    VALUES('%s', %d, '%s')",
 658                    $row->$sourcekey, $error['level'], $error['message']);
 659        }
 660        if ($success) {
 661          $imported++;
 662        }
 663      }
 664      else {
 665        $imported++;
 666      }
 667      timer_start('db_fetch_object');
 668    }
 669    timer_stop('db_fetch_object');
 670  
 671    if ($status == MIGRATE_STATUS_IN_PROGRESS) {
 672      $status = MIGRATE_STATUS_SUCCESS;
 673    }
 674    $message = _migrate_progress_message($lastfeedback, $imported, $description, TRUE, $status);
 675    if ($status == MIGRATE_STATUS_SUCCESS) {
 676      // Remember we're done
 677      if ($importing) {
 678        $tblinfo->importing = 0;
 679      }
 680      $tblinfo->lastimported = date('Y-m-d H:i:s');
 681    }
 682    if (isset($feedback)) {
 683      $feedback($message);
 684    }
 685    else {
 686      $messages[] = $message;
 687    }
 688    watchdog('migrate', $message);
 689    if (!$original_importing) {
 690      $sql = "UPDATE {migrate_content_sets} SET importing=0 WHERE mcsid=%d";
 691      db_query($sql, $mcsid);
 692    }
 693  
 694    $tblinfo->semaphore = FALSE;
 695    drupal_write_record('migrate_content_sets', $tblinfo, 'mcsid');
 696  
 697    return $status;
 698  }
 699  
 700  /* Revisit
 701  function migrate_content_process_all_action(&$dummy, $action_context, $a1, $a2) {
 702    migrate_content_process_all(time());
 703  }
 704   */
 705  
 706  function migrate_content_process_all_batch($starttime, $limit, $idlist, &$context) {
 707    $messages = array();
 708  
 709    // A zero max_execution_time means no limit - but let's set a reasonable
 710    // limit anyway
 711    $starttime = time();
 712    $maxexectime = ini_get('max_execution_time');
 713    if (!$maxexectime) {
 714      $maxexectime = 240;
 715    }
 716  
 717    // Initialize the Batch API context
 718    $context['finished'] = 0;
 719  
 720    // The Batch API progress bar will reflect the number of operations being
 721    // done (clearing/importing/scanning)
 722    if (!isset($context['sandbox']['numops'])) {
 723      $numops = 0;
 724      $sql = "SELECT COUNT(*) FROM {migrate_content_sets} WHERE clearing=1";
 725      $numops = db_result(db_query($sql));
 726      $sql = "SELECT COUNT(*) FROM {migrate_content_sets} WHERE importing=1 OR scanning=1";
 727      $numops += db_result(db_query($sql));
 728      $context['sandbox']['numops'] = $numops;
 729      $context['sandbox']['numopsdone'] = 0;
 730    }
 731  
 732    // For the timelimit, subtract more than enough time to clean up
 733    $options = array(
 734      'itemlimit' => $limit,
 735      'timelimit' => $starttime + (($maxexectime < 20) ? $maxexectime : ($maxexectime - 20)),
 736      'idlist' => $idlist,
 737    );
 738    $status = migrate_content_process_all($messages, $options);
 739    foreach ($messages as $message) {
 740      if (!isset($context['sandbox']['message'])) {
 741        $context['sandbox']['message'] = $message . '<br />';
 742      }
 743      else {
 744        $context['sandbox']['message'] .= $message . '<br />';
 745      }
 746      $context['message'] = $context['sandbox']['message'];
 747      $context['results'][] = $message;
 748      $context['sandbox']['numopsdone'] += $options['opcount'];
 749    }
 750  
 751    // If we did not arrive via a timeout, we must have finished all operations
 752    if ($status != MIGRATE_STATUS_TIMEDOUT) {
 753      $context['finished'] = 1;
 754    }
 755    else {
 756      // Not done, report what percentage done we are (in terms of number of operations)
 757      $context['finished'] = $context['sandbox']['numopsdone']/$context['sandbox']['numops'];
 758    }
 759  
 760    // If requested save timers for eventual display
 761    if (variable_get('migrate_display_timers', 0)) {
 762      global $timers;
 763      foreach ($timers as $name => $timerec) {
 764        if (isset($timerec['time'])) {
 765          $context['sandbox']['times'][$name] += $timerec['time']/1000;
 766        }
 767      }
 768      // When all done, display the timers
 769      if ($context['finished'] == 1 && isset($context['sandbox']['times'])) {
 770        global $timers;
 771        arsort($context['sandbox']['times']);
 772        foreach ($context['sandbox']['times'] as $name => $total) {
 773          drupal_set_message("$name: " . round($total, 2));
 774        }
 775      }
 776    }
 777  }
 778  
 779  /**
 780   * Process all enabled migration processes
 781   *
 782   * @param $messages
 783   *  Array of messages to (ultimately) be displayed by the caller.
 784   * @param $options
 785   *  Keyed array of optional options:
 786   *    itemlimit - Maximum number of items to process
 787   *    timelimit - Unix timestamp after which to stop processing
 788   *    idlist - Comma-separated list of source IDs to process, instead of proceeding through
 789   *      all unmigrated rows
 790   *    opcount - Number of clearing or import operations performed
 791   *    feedback - Keyed array controlling status feedback to the caller
 792   *      function - PHP function to call, passing a message to be displayed
 793   *      frequency - How often to call the function
 794   *      frequency_unit - How to interpret frequency (items or seconds)
 795   *
 796   * @return
 797   *  Status of the migration process:
 798   */
 799  function migrate_content_process_all(&$messages = array(), &$options = array()) {
 800    // First, perform any clearing actions in reverse order
 801    $result = db_query("SELECT mcsid
 802                        FROM {migrate_content_sets}
 803                        WHERE clearing=1
 804                        ORDER BY weight DESC");
 805    $context['sandbox']['timedout'] = FALSE;
 806    if (!isset($options['opcount'])) {
 807      $options['opcount'] = 0;
 808    }
 809    while ($row = db_fetch_object($result)) {
 810      $status = migrate_content_process_clear($row->mcsid, $messages, $options);
 811      if ($status != MIGRATE_STATUS_SUCCESS) {
 812        break;
 813      }
 814      $options['opcount']++;
 815    }
 816  
 817    // Then, any import actions going forward
 818    $result = db_query("SELECT mcsid
 819                        FROM {migrate_content_sets}
 820                        WHERE importing=1 OR scanning=1
 821                        ORDER BY weight");
 822    while ($row = db_fetch_object($result)) {
 823      $status = migrate_content_process_import($row->mcsid, $messages, $options);
 824      if ($status != MIGRATE_STATUS_SUCCESS) {
 825        break;
 826      }
 827      $options['opcount']++;
 828    }
 829  
 830    return $status;
 831  }
 832  
 833  function _migrate_progress_message($starttime, $numitems, $description, $import = TRUE, $status = MIGRATE_STATUS_SUCCESS) {
 834    $time = (microtime(TRUE) - $starttime);
 835    if ($time > 0) {
 836      $perminute = round(60*$numitems/$time);
 837      $time = round($time, 1);
 838    }
 839    else {
 840      $perminute = '?';
 841    }
 842  
 843    if ($import) {
 844      switch ($status) {
 845        case MIGRATE_STATUS_SUCCESS:
 846          $basetext = "!numitems items imported in !time seconds (!perminute/min) - done importing '!description'";;
 847          break;
 848        case MIGRATE_STATUS_FAILURE:
 849          $basetext = "!numitems items imported in !time seconds (!perminute/min) - failure importing '!description'";;
 850          break;
 851        case MIGRATE_STATUS_TIMEDOUT:
 852        case MIGRATE_STATUS_IN_PROGRESS:
 853          $basetext = "!numitems items imported in !time seconds (!perminute/min) - continuing importing '!description'";
 854          break;
 855        case MIGRATE_STATUS_CANCELLED:
 856          $basetext = "!numitems items imported in !time seconds (!perminute/min) - cancelled importing '!description'";
 857          break;
 858      }
 859    }
 860    else {
 861      switch ($status) {
 862        case MIGRATE_STATUS_SUCCESS:
 863          $basetext = "!numitems previously-imported items deleted in !time seconds (!perminute/min) - done clearing '!description'";;
 864          break;
 865        case MIGRATE_STATUS_FAILURE:
 866          $basetext = "!numitems previously-imported items deleted in !time seconds (!perminute/min) - failure clearing '!description'";;
 867          break;
 868        case MIGRATE_STATUS_TIMEDOUT:
 869        case MIGRATE_STATUS_IN_PROGRESS:
 870          $basetext = "!numitems previously-imported items deleted in !time seconds (!perminute/min) - continuing clearing '!description'";
 871          break;
 872        case MIGRATE_STATUS_CANCELLED:
 873          $basetext = "!numitems previously-imported items deleted in !time seconds (!perminute/min) - cancelled clearing '!description'";
 874          break;
 875      }
 876    }
 877    $message = t($basetext,
 878        array('!numitems' => $numitems, '!time' => $time, '!perminute' => $perminute,
 879              '!description' => $description));
 880  
 881    return $message;
 882  }
 883  
 884  /*
 885   * Implementation of hook_init().
 886   */
 887  function migrate_init() {
 888    // Loads the hooks for the supported modules.
 889    // TODO: Be more lazy - only load when really needed
 890    $path = drupal_get_path('module', 'migrate') .'/modules';
 891    $files = drupal_system_listing('.*\.inc$', $path, 'name', 0);
 892    foreach ($files as $module_name => $file) {
 893      if (module_exists($module_name)) {
 894        include_once($file->filename);
 895      }
 896    }
 897  
 898    // Add main CSS functionality.
 899    drupal_add_css(drupal_get_path('module', 'migrate') .'/migrate.css');
 900  }
 901  
 902  /**
 903   * Implementation of hook_action_info().
 904   */
 905  /* Revisit
 906  function migrate_action_info() {
 907    $info['migrate_content_process_clear'] = array(
 908      'type' => 'migrate',
 909      'description' => t('Clear a migration content set'),
 910      'configurable' => FALSE,
 911      'hooks' => array(
 912        'cron' => array('run'),
 913      ),
 914    );
 915    $info['migrate_content_process_import'] = array(
 916      'type' => 'migrate',
 917      'description' => t('Import a migration content set'),
 918      'configurable' => FALSE,
 919      'hooks' => array(
 920        'cron' => array('run'),
 921      ),
 922    );
 923    $info['migrate_content_process_all_action'] = array(
 924      'type' => 'migrate',
 925      'description' => t('Perform all active migration processes'),
 926      'configurable' => FALSE,
 927      'hooks' => array(
 928        'cron' => array('run'),
 929      ),
 930    );
 931    return $info;
 932  }
 933   */
 934  
 935  /**
 936   * Implementation of hook_cron().
 937   */
 938  function migrate_cron() {
 939    if (variable_get('migrate_enable_cron', 0)) {
 940      $path = drupal_get_path('module', 'migrate') . '/migrate_pages.inc';
 941      include_once($path);
 942      // Elevate privileges so node deletion/creation works in cron
 943      session_save_session(FALSE);
 944      global $user;
 945      $saveuser = $user;
 946      $user = user_load(array('uid' => 1));
 947      $messages = array();
 948      // A zero max_execution_time means no limit - but let's set a reasonable
 949      // limit anyway
 950      $starttime = variable_get('cron_semaphore', 0);
 951      $maxexectime = ini_get('max_execution_time');
 952      if (!$maxexectime) {
 953        $maxexectime = 240;
 954      }
 955      $options = array('timelimit' => $starttime + (($maxexectime < 20) ? $maxexectime : ($maxexectime - 20)));
 956      migrate_content_process_all($messages, $options);
 957      $user = $saveuser;
 958      session_save_session(TRUE);
 959    }
 960  }
 961  
 962  /**
 963   * Implementation of hook_perm().
 964   */
 965  function migrate_perm() {
 966    return array(MIGRATE_ACCESS_BASIC, MIGRATE_ACCESS_ADVANCED);
 967  }
 968  
 969  /**
 970   * Implementation of hook_help().
 971   */
 972  function migrate_help($page, $arg) {
 973    switch ($page) {
 974      case 'admin/content/migrate':
 975        return theme('advanced_help_topic', 'migrate', 'about', 'icon') .
 976          t('Click the question marks like this one to read the migrate module help topics.');
 977      case 'admin/content/migrate/content_sets':
 978        return t('Define sets of mappings from imported tables to Drupal content. These are the
 979          migrations which are later processed.');
 980      case 'admin/content/migrate/process':
 981        return t('View and manage import processes here. Processes that are enabled for processing
 982          are checked - they can be cancelled by unchecking, or new processes begun by checking,
 983          then clicking Submit. Any checked process will run in the background (via cron)
 984          automatically - you may also run them interactively or in drush. A process that is
 985          actively running will be <span class="migrate-running">highlighted</span>.');
 986      case 'admin/content/migrate/tools':
 987        return t('Besides content that is migrated into a new site, nodes may be manually
 988          created during the testing process. Typically you will want to clear these before the
 989          final migration - if you are <strong>absolutely positive</strong> that all nodes of a
 990          given type should be deleted, you may do so here.');
 991    }
 992  }
 993  
 994  /**
 995   * Implementation of hook_menu().
 996   */
 997  function migrate_menu() {
 998    $items = array();
 999  
1000    $items['admin/content/migrate'] = array(
1001      'title' => 'Migrate',
1002      'description' => 'Manage data migration from external sources',
1003      'page callback' => 'migrate_front',
1004      'access arguments' => array(MIGRATE_ACCESS_BASIC),
1005      'file' => 'migrate_pages.inc',
1006    );
1007    $items['admin/content/migrate/content_sets'] = array(
1008      'title' => 'Content sets',
1009      'description' => 'Manage content sets: mappings of source data to Drupal content',
1010      'weight' => 2,
1011      'page callback' => 'migrate_content_sets',
1012      'access arguments' => array(MIGRATE_ACCESS_ADVANCED),
1013      'file' => 'migrate_pages.inc',
1014    );
1015    $items['admin/content/migrate/process'] = array(
1016      'title' => 'Process',
1017      'description' => 'Perform and monitor the creation of Drupal content from source data',
1018      'weight' => 3,
1019      'page callback' => 'migrate_dashboard',
1020      'access arguments' => array(MIGRATE_ACCESS_BASIC),
1021      'file' => 'migrate_pages.inc',
1022    );
1023    $items['admin/content/migrate/tools'] = array(
1024      'title' => 'Tools',
1025      'description' => 'Additional tools for managing migration',
1026      'weight' => 4,
1027      'page callback' => 'migrate_tools',
1028      'access arguments' => array(MIGRATE_ACCESS_ADVANCED),
1029      'file' => 'migrate_pages.inc',
1030    );
1031    $items['admin/content/migrate/settings'] = array(
1032      'title' => 'Settings',
1033      'description' => 'Migrate module settings',
1034      'weight' => 5,
1035      'page callback' => 'migrate_settings',
1036      'access arguments' => array(MIGRATE_ACCESS_ADVANCED),
1037      'file' => 'migrate_pages.inc',
1038    );
1039    $items['admin/content/migrate/content_sets/%'] = array(
1040      'title' => 'Content set',
1041      'page callback' => 'drupal_get_form',
1042      'page arguments' => array('migrate_content_set_mappings', 4),
1043      'access arguments' => array(MIGRATE_ACCESS_ADVANCED),
1044      'type' => MENU_CALLBACK,
1045      'file' => 'migrate_pages.inc',
1046    );
1047    $items['migrate/xlat/%'] = array(
1048      'page callback' => 'migrate_xlat',
1049      'access arguments' => array('access content'),
1050      'page arguments' => array(2),
1051      'type' => MENU_CALLBACK,
1052    );
1053    return $items;
1054  }
1055  
1056  /**
1057   * Implementation of hook_schema_alter().
1058   * @param $schema
1059   */
1060  function migrate_schema_alter(&$schema) {
1061    // Check for table existence - at install time, hook_schema_alter() may be called
1062    // before our install hook.
1063    if (db_table_exists('migrate_content_sets')) {
1064      $result = db_query("SELECT * FROM {migrate_content_sets}");
1065      while ($content_set = db_fetch_object($result)) {
1066        $maptablename = migrate_map_table_name($content_set->mcsid);
1067        $msgtablename = migrate_message_table_name($content_set->mcsid);
1068  
1069        // Get the proper field definition for the sourcekey
1070        $view = views_get_view($content_set->view_name);
1071        if (!$view) {
1072          drupal_set_message(t('View !view does not exist - either (re)create this view, or
1073            remove the migrate content set using it.', array('!view' => $content_set->view_name)));
1074          continue;
1075        }
1076        // Must do this to load the database
1077        $view->init_query();
1078  
1079        // TODO: For now, PK must be in base_table
1080        if (isset($view->base_database)) {
1081          $tabledb = $view->base_database;
1082        }
1083        else {
1084          $tabledb = 'default';
1085        }
1086        $tablename = $view->base_table;
1087        db_set_active($tabledb);
1088        $inspect = schema_invoke('inspect');
1089        db_set_active('default');
1090        $sourceschema = $inspect[$tablename];
1091        // If the PK of the content set is defined, make sure we have a mapping table
1092        if ($sourcekey = $content_set->sourcekey) {
1093          $sourcefield = $sourceschema['fields'][$sourcekey];
1094          if (!$sourcefield) {
1095            // strip base table name if views prepended it
1096            $baselen = drupal_strlen($tablename);
1097            if (!strncasecmp($sourcekey, $tablename . '_', $baselen + 1)) {
1098              $sourcekey = drupal_substr($sourcekey, $baselen + 1);
1099            }
1100            $sourcefield = $sourceschema['fields'][$sourcekey];
1101          }
1102          // We don't want serial fields to behave serially, so change to int
1103          if ($sourcefield['type'] == 'serial') {
1104            $sourcefield['type'] = 'int';
1105          }
1106          $schema[$maptablename] = _migrate_map_table_schema($sourcefield);
1107          $schema[$msgtablename] = _migrate_message_table_schema($sourcefield);
1108        }
1109      }
1110    }
1111  }
1112  
1113  /*
1114   * Translate URIs from an old site to the new one
1115   * Requires adding RewriteRules to .htaccess. For example, if the URLs
1116   * for news articles had the form
1117   * http://example.com/issues/news/[OldID].html, use this rule:
1118   *
1119   * RewriteRule ^issues/news/([0-9]+).html$ /migrate/xlat/node/$1 [L]
1120   */
1121  function migrate_xlat($contenttype, $oldid) {
1122    $uri = '';
1123    if ($contenttype && $oldid) {
1124      $newid = _migrate_xlat_get_new_id($contenttype, $oldid);
1125      if ($newid) {
1126        $uri = migrate_invoke_all("xlat_$contenttype", $newid);
1127        drupal_goto($uri[0], NULL, NULL, 301);
1128      }
1129    }
1130  }
1131  
1132  /*
1133   * Helper function to translate an ID from a source file to the corresponding
1134   * Drupal-side ID (nid, uid, etc.)
1135   * Note that the result may be ambiguous - for example, if you are importing
1136   * nodes from different content sets, they might have overlapping source IDs.
1137   */
1138  function _migrate_xlat_get_new_id($contenttype, $oldid) {
1139    $result = db_query("SELECT mcsid
1140                        FROM {migrate_content_sets}
1141                        WHERE contenttype='%s'",
1142                       $contenttype);
1143    while ($row = db_fetch_object($result)) {
1144      static $maptables = array();
1145      if (!isset($maptables[$row->mcsid])) {
1146        $maptables[$row->mcsid] = migrate_map_table_name($row->mcsid);
1147      }
1148      $sql = "SELECT destid
1149              FROM {" . $maptables[$row->mcsid] . "}
1150              WHERE sourceid='%s'";
1151      $id = db_result(db_query($sql, $oldid));
1152      if ($id) {
1153        return $id;
1154      }
1155    }
1156    return NULL;
1157  }
1158  
1159  /**
1160   * Implementation of hook_theme().
1161   *
1162   * Registers all theme functions used in this module.
1163   */
1164  function migrate_theme() {
1165    return array(
1166      'migrate_mapping_table' => array('arguments' => array('form')),
1167      '_migrate_dashboard_form' => array(
1168        'arguments' => array('form' => NULL),
1169        'function' => 'theme_migrate_dashboard',
1170      ),
1171      '_migrate_tools_form' => array(
1172        'arguments' => array('form' => NULL),
1173        'function' => 'theme_migrate_tools',
1174      ),
1175      '_migrate_settings_form' => array(
1176        'arguments' => array('form' => NULL),
1177        'function' => 'theme_migrate_settings',
1178      ),
1179      'migrate_content_set_mappings' => array(
1180        'arguments' => array('form' => NULL),
1181        'function' => 'theme_migrate_content_set_mappings',
1182      ),
1183    );
1184  }
1185  
1186  function migrate_map_table_name($mcsid) {
1187    return "migrate_map_$mcsid";
1188  }
1189  
1190  function migrate_message_table_name($mcsid) {
1191    return "migrate_msgs_$mcsid";
1192  }
1193  
1194  function _migrate_map_table_name($mcsid) {
1195    return migrate_map_table_name($mcsid);
1196  }
1197  
1198  function _migrate_message_table_name($mcsid) {
1199    return migrate_message_table_name($mcsid);
1200  }
1201  
1202  function _migrate_map_table_schema($sourcefield) {
1203    $schema = array(
1204      'description' => t('Mappings from source key to destination key'),
1205      'fields' => array(
1206        'sourceid' => $sourcefield,
1207        // @TODO: Assumes destination key is unsigned int
1208        'destid' => array(
1209          'type' => 'int',
1210          'unsigned' => TRUE,
1211          'not null' => TRUE,
1212        ),
1213      ),
1214      'primary key' => array('sourceid'),
1215      'indexes' => array(
1216        'idkey' => array('destid'),
1217      ),
1218    );
1219    return $schema;
1220  }
1221  
1222  function _migrate_message_table_schema($sourcefield) {
1223    $schema = array(
1224      'description' => t('Import errors'),
1225      'fields' => array(
1226        'mceid' => array(
1227          'type' => 'serial',
1228          'unsigned' => TRUE,
1229          'not null' => TRUE,
1230        ),
1231        'sourceid' => $sourcefield,
1232        'level' => array(
1233          'type' => 'int',
1234          'unsigned' => TRUE,
1235          'not null' => TRUE,
1236           'default' => 1,
1237        ),
1238        'message' => array(
1239          'type' => 'text',
1240          'size' => 'medium',
1241          'not null' => TRUE,
1242        ),
1243      ),
1244      'primary key' => array('mceid'),
1245      'indexes' => array(
1246        'sourceid' => array('sourceid'),
1247      ),
1248    );
1249    return $schema;
1250  }
1251  
1252  function migrate_views_api() {
1253    return array('api' => '2.0');
1254  }
1255  
1256  /**
1257   * Check to see if the advanced help module is installed, and if not put up
1258   * a message.
1259   *
1260   * Only call this function if the user is already in a position for this to
1261   * be useful.
1262   */
1263  function migrate_check_advanced_help() {
1264    if (variable_get('migrate_hide_help_message', FALSE)) {
1265      return;
1266    }
1267  
1268    if (!module_exists('advanced_help')) {
1269      $filename = db_result(db_query("SELECT filename FROM {system} WHERE type = 'module' AND name = 'advanced_help'"));
1270      if ($filename && file_exists($filename)) {
1271        drupal_set_message(t('If you <a href="@modules">enable the advanced help module</a>,
1272                Migrate will provide more and better help. <a href="@hide">Hide this message.</a>',
1273                array('@modules' => url('admin/build/modules'),
1274          '@hide' => url('admin/build/views/tools'))));
1275      }
1276      else {
1277        drupal_set_message(t('If you install the advanced help module from !href, Migrate will provide more and better help. <a href="@hide">Hide this message.</a>', array('!href' => l('http://drupal.org/project/advanced_help', 'http://drupal.org/project/advanced_help'), '@hide' => url('admin/content/migrate/settings'))));
1278      }
1279    }
1280  }
1281  
1282  /**
1283   * Check if a date is valid and return the correct
1284   * timestamp to use. Returns -1 if the date is not
1285   * considered valid.
1286   */
1287  function _migrate_valid_date($date) {
1288    //TODO: really check whether the date is valid!!
1289    if (empty($date)) {
1290      return -1;
1291    }
1292  
1293    if (is_numeric($date) && $date > -1) {
1294      return $date;
1295    }
1296    // strtotime() doesn't recognize dashes as separators, change to slashes
1297    $date = str_replace('-', '/', $date);
1298  
1299    $time = strtotime($date);
1300    if ($time < 0 || !$time) {
1301      // Handles form YYYY-MM-DD HH:MM:SS.garbage
1302      if (drupal_strlen($date) > 19) {
1303        $time = strtotime(drupal_substr($date, 0, 19));
1304        if ($time < 0 || !$time) {
1305          return -1;
1306        }
1307      }
1308    }
1309    return $time;
1310  }
1311  


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7