# This patch file was generated by NetBeans IDE
# This patch can be applied using context Tools: Apply Diff Patch action on respective folder.
# It uses platform neutral UTF-8 encoding.
# Above lines and this line are ignored by the patching process.
Index: contributions/modules/millennium/contrib/millennium_filter/millennium_filter.module
--- contributions/modules/millennium/contrib/millennium_filter/millennium_filter.module Base (1.1.2.5)
+++ contributions/modules/millennium/contrib/millennium_filter/millennium_filter.module Locally Modified (Based On 1.1.2.5)
@@ -111,6 +111,13 @@
         ),
         '#default_value' => variable_get('millennium_filter_report_errors', array('watchdog' => 'watchdog')),
       );
+      $form['millennium_filter']['millennium_filter_default_base_url'] = array(
+        '#type' => 'select',
+        '#title' => t('Default source WebOPAC'),
+        '#description' => t('Select one of the enabled sources to use when only record numbers are issued.'),
+        '#default_value' => variable_get('millennium_filter_default_base_url', ''),
+        '#options' => drupal_map_assoc(array_merge(array(0 => t('<none>')), array_keys(variable_get('millennium_sources', array())))),
+      );
       $form['millennium_filter']['millennium_filter_remove_fields'] = array(
         '#type' => 'checkboxes',
         '#title' => t('Fields to hide in shown records'),
@@ -153,7 +160,17 @@
     if (!preg_match(MILLENNIUM_RECORD_PREG_NOCHECKDIGIT, $id)) {
       return false;
     }
-    $base_url = millennium_get_real_baseurl();
\ No newline at end of file
+    // Pick the OPAC to use
+    $base_url = variable_get('millennium_filter_default_base_url', '');
+    if (!$base_url) {
+      // If only one source defined, use that one as default
+      $sources = variable_get('millennium_sources', array());
+      if (sizeof($sources) == 0) {
+        return false;
+      }
+      // Put the index of the first (and only) element of $source into $base_url
+      foreach ($sources as $base_url => $dummy);
+    }
\ No newline at end of file
     $recnum = $id;
   }
   
Index: contributions/modules/millennium/millennium.admin.inc
--- contributions/modules/millennium/millennium.admin.inc Base (1.1.2.31)
+++ contributions/modules/millennium/millennium.admin.inc Locally Modified (Based On 1.1.2.31)
@@ -9,8 +9,10 @@
  * Callback for drupal_get_form for general settings form
  */
 function millennium_admin_settings() {
-  if (variable_get('millennium_webopac_baseurl', false) === false) {
-    drupal_set_message(t('You have not yet set the Base URL.'), 'warning');
+  $form = array();
+  $sources = millennium_get_sources_count();
+  if (sizeof($sources) == 0) {
+    drupal_set_message(t('You have not yet set any sources.'), 'warning');
   }
 
   $node_types = node_get_types();
@@ -33,22 +35,6 @@
     '#default_value' => variable_get('millennium_marc_node_type', 'page'),
   );
 
-  $form['general']['millennium_webopac_baseurl'] = array(
-    '#type' => 'textfield',
-    '#title' => t("Base URL for WebOPAC"),
-    '#size' => 40,
-    '#default_value' => variable_get('millennium_webopac_baseurl', ''),
-    '#description' => t('For example: http://example.com/'),
-    '#prefix' => '<table id="baseurl-table"><tbody><tr><td>',
-    '#suffix' => '</td><td id="checkurl"></td></tr></tbody></table>',
-    '#ahah' => array(
-      'path' => 'millennium_checkurl_js',
-      'event' => 'change',
-      'wrapper' => 'checkurl',
-      'method' => 'replace',
-      'effect' => 'fade',
-    ),
-  );
   $form['general']['millennium_action_on_last_item_deleted'] = array(
     '#type' => 'radios',
     '#title' => t('Action when imported records are no longer found in WebOPAC'),
@@ -56,7 +42,170 @@
     '#default_value' => variable_get('millennium_action_on_last_item_deleted', 'unpublish'),
   );
   
-  $run_cron_url = l(t('run cron'), 'admin/reports/status/run-cron', array('query' => array('destination' => MILLENNIUM_SETTINGS_PATH)));
+  // Sources fieldset
+  $form['sources'] = array(
+    '#type' => 'fieldset',
+    '#collapsible' => true,
+    '#title' => t('Source OPACs'),
+    '#description' => t('You can enter OPAC sources that will be available for batch import and other operations.'),
+  );
+
+  // Show table for existing OPAC sources
+  $rows = array();
+  $headers = array(t('Action'), t('OPAC base URL'), t('OPAC name'), t('Import stats'));
+  foreach ($sources as $base_url => $source_data) {
+    // Element to remove this source
+    $remove_element = array(
+      '#name' => 'remove_source_' . $base_url,
+      '#type' => 'checkbox',
+      '#title' => t('Remove')
+    );
+    // Element to edit existing source name
+    $edit_name_element = array(
+      '#name' => 'edit_name_' . $base_url,
+      '#type' => 'textfield',
+      '#value' => $source_data["name"],
+      '#size' => 30,
+    );
+    // Add row to table
+    $rows[] = array(
+      drupal_render($remove_element),
+      l($base_url, $base_url),
+      drupal_render($edit_name_element),
+      t('@count nodes', array('@count' => $sources[$base_url]['node_count'] + 0)),
+    );
+  }
+
+  // Add a final table row to allow adding new sources
+  $add_url_element = array(
+    '#name' => 'add_url',
+    '#type' => 'textfield',
+    '#size' => 30,
+    '#required' => TRUE,
+  );
+  $add_name_element = array(
+    '#name' => 'add_name',
+    '#type' => 'textfield',
+    '#size' => 30,
+  );
+  $rows[] = array(t('Add new source:'), drupal_render($add_url_element), drupal_render($add_name_element));
+  // Add the themed table to the form
+  $form['sources']['table'] = array(
+    '#type' => 'markup',
+    '#value' => theme('table', $headers, $rows)
+  );
+  // Add custom validation and submission callbacks only for the table elements;
+  // the rest of the form is automatically handled by
+  // system_settings_form_submit() because of system_settings_form() call below.
+  $form['#validate'][] = 'millennium_admin_settings_form_validate';
+  $form['#submit'][] = 'millennium_admin_settings_form_submit';
+
+  // Render as a system settings form.
+  return system_settings_form($form);
+}
+
+/**
+ * Validate handler for settings form; handles special values in table.
+ */
+function millennium_admin_settings_form_validate($form, &$form_state) {
+  $sources = variable_get('millennium_sources', array());
+
+  // Validate sources to remove
+  foreach ($sources as $base_url => $source_data) {
+    $base_url_as_index = str_replace(".", "_", $base_url);
+    if ($form_state['clicked_button']['#post']["remove_source_{$base_url_as_index}"]) {
+      // Check if there are existing records for this base_url
+      $count = (int)db_result(db_query("SELECT count(1) FROM {millennium_node_bib} WHERE base_url = '%s'", $base_url));
+      if ($count) {
+        form_set_error("remove_source_{$base_url_as_index}", t('The source %url cannot be removed since nodes that depend on it still exist.', array('%url' => $new_url)));
+      }
+    }
+  }
+
+  // Validate a new URL if given
+  $new_url = trim($form_state['clicked_button']['#post']['add_url']);
+  $new_name = trim($form_state['clicked_button']['#post']['add_name']);
+  if ($new_url) {
+    // Normalize the base url, eliminate markup or XSS
+    $new_url = check_plain(millennium_get_real_baseurl($new_url));
+
+    // Check for valid URL
+    if (strpos($new_url, 'http://') !== 0) {
+      form_set_error('add_url', t('The entered url %url is not valid.', array('%url' => $new_url)));
+      return;
+    }
+
+    // Check for unique base URL
+    if (isset($sources[$new_url])) {
+      form_set_error('add_url', t('The url %url is already defined.', array('%url' => $new_url)));
+      return;
+    }
+
+    // Ping the WebOPAC, alert if not pingable
+    if (!millennium_ping($new_url)) {
+      drupal_set_message(t('Warning: The WebOPAC at !url is not responding.', array('!url' => l($new_url, $new_url))));
+    }
+  }
+  // All is OK, store the normalized URL for the submit function
+  $form_state['storage']['add_url'] = $new_url;
+}
+
+/**
+ * Submit handler for settings form; handles special values that are not handled
+ * by system_settings_form_submit();
+ */
+function millennium_admin_settings_form_submit($form, &$form_state) {
+  $sources = variable_get('millennium_sources', array());
+
+  // Update any changes to sources
+  foreach ($sources as $base_url => $source_data) {
+    $base_url_as_index = str_replace(".", "_", $base_url);
+    // Removal checked?
+    if ($form_state['clicked_button']['#post']["remove_source_{$base_url_as_index}"] == "1") {
+      drupal_set_message(t('Removed source %url', array('%url' => $base_url)));
+      unset($sources[$base_url]);
+      continue;
+    }
+
+    // Name change
+    $sent_name = check_plain(trim($form_state['clicked_button']['#post']["edit_name_{$base_url_as_index}"]));
+    if ($sent_name != $source_data["name"]) {
+      // Update taxonomy term if mapping currently set
+      $opacname_vid = variable_get('millennium_opacname_vid', -1);
+      if ($opacname_vid != -1) {
+        // Find and load existing term
+        $tid = db_result(db_query('SELECT tid FROM {term_data} WHERE vid=%d and name="%s"', $opacname_vid, $source_data["name"]));
+        $term = taxonomy_get_term($tid);
+        $term->name = $sent_name;
+        // Save term's new name
+        $term = (array)$term;
+        taxonomy_save_term($term);
+        if ($sent_name != '') {
+          drupal_set_message(t('Updated taxonomy term for %opacname.', array('%opacname' => $sent_name)));
+        }
+      }
+      // Set name in $sources array
+      $sources[$base_url]["name"] = $sent_name;
+    }
+  }
+
+  // If sent, add the new url and name to the sources
+  $new_url = $form_state['storage']['add_url'];
+  $new_name = trim($form_state['clicked_button']['#post']['add_name']);
+  if ($new_url) {
+    $sources[$new_url] = array('name' => check_plain($new_name));
+  }
+
+  // Store the new value for sources
+  variable_set('millennium_sources', $sources);
+}
+
+/**
+ * Callback for drupal_get_form for crawl settings form.
+ */
+function millennium_admin_crawl() {
+  $form = array();
+  $run_cron_url = l(t('run cron'), 'admin/reports/status/run-cron', array('query' => array('destination' => MILLENNIUM_SETTINGS_PATH . '/crawl')));
   $form['crawl'] = array(
     '#type' => 'fieldset',
     '#collapsible' => true,
@@ -71,6 +220,13 @@
     '#description' => t('Enabling this will start (or continue) the automatic record import/update. Disabling this does not stop importing items queued manually for import.'),
     '#default_value' => variable_get('millennium_crawl_flag', 0),
   );
+  $form['crawl']['millennium_crawl_baseurl'] = array(
+    '#type' => 'select',
+    '#title' => t('Source WebOPAC'),
+    '#description' => t('Select one of the enabled sources.'),
+    '#default_value' => variable_get('millennium_crawl_baseurl', ''),
+    '#options' => drupal_map_assoc(array_merge(array(0 => t('<none>')), array_keys(variable_get('millennium_sources', array())))),
+  );
   $form['crawl']['millennium_webopac_maxrecords'] = array(
     '#type' => 'select',
     '#title' => t("Items to import per cron run"),
@@ -106,7 +262,6 @@
     '#description' => t('WARNING: Set ONLY if you want to start over from that number onwards.'),
     '#default_value' => variable_get('millennium_webopac_reset', false),
   );
-
   return system_settings_form($form);
 }
 
@@ -130,6 +285,13 @@
     '#description' => t('WARNING: This option might slow down your page views, as availability information is fetched from the Millennium for each record (and kept in a Drupal cache for subsequent calls). Recommended setting is "Full view only".'),
   );
 
+  $form['holdings']['millennium_holdings_opacname'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show OPAC name in record holdings table'),
+    '#description' => t('Enabling this will show the OPAC name entered in the Sources tab next to each item in the holdings table.'),
+    '#default_value' => variable_get('millennium_holdings_opacname', 1),
+  );
+
   $form['holdings']['millennium_preferred_locations'] = array(
     '#type' => 'textfield',
     '#title' => '('. t('Optional') .') '. t('Locations to show in teaser view availability (comma-separated)'),
@@ -194,18 +356,18 @@
     '#value' =>
       t('If you want to browse your imported records via taxonomy, you must assign a mapping here.')
       . '<ul><li>'
-      . t('You can only map to taxonomy vocabularies that have a "Tags" setting.')
+      . t('You can only map to taxonomy vocabularies that have a "Tags" setting and are assigned to the destination node type (currently set to %type in the !sourceslink tab).', array('%type' => variable_get('millennium_marc_node_type', '<none>'), '!sourceslink' => l(t('sources'), 'admin/settings/millennium')))
       . '</li><li>'
       . t('NOTE: CHANGING these values after import does not change already-imported records! You can use the !link page to refresh mappings using already-imported data.', array('!link' => l('Millennium batch import/refresh', 'admin/content/millennium')))
       . '</li></ul>',
   );
-  $form['vids'] = array(
+  $form['marc'] = array(
     '#type' => 'fieldset',
     '#title' => t("MARC to taxonomy mappings"),
     '#collapsible' => false,  // DUE to problems http://drupal.org/node/152431
   );
 
-  $form['vids']['millennium_taxonomy_language'] = array(
+  $form['marc']['millennium_taxonomy_language'] = array(
     '#type' => 'radios',
     '#title' => t('Language used to create taxonomy terms and other additional information'),
     '#options' => array(
@@ -221,7 +383,7 @@
 
   // Issue warning when no applicable vocabularies are available
   if (sizeof($v_options) == 1) {
-    $form['vids']['warning'] = array(
+    $form['marc']['warning'] = array(
       '#type' => 'markup',
       '#prefix' => '<div class="warning">',
       '#suffix' => '</div>',
@@ -260,17 +422,31 @@
 
   foreach ($vocs as $code => $voc_name) {
     $name = "millennium_marc_vid_$code";
-    $form['vids'][$name] = array(
+    $form['marc'][$name] = array(
       '#type' => 'select',
-      '#prefix' => '<tr><td style="width:30%">' . $voc_name . '<div class=description>'. $descs[$code].'</div><td>' . t("into") . '<td>',
+      '#prefix' => '<tr><td style="width:30%">' . $voc_name . '<div class=description>'. $descs[$code].'</div></td><td>' . t("into") . '</td><td>',
       '#suffix' => '</td></tr>',
       '#options' => $v_options,
       '#default_value' => variable_get($name, -1)
     );
   }
 
+  // Fieldset for OPAC Name
+  $form['opacname'] = array(
+    '#prefix' => '</table></fieldset>',
+    '#type' => 'fieldset',
+    '#title' => t("OPAC name to taxonomy mapping"),
+    '#collapsible' => false,
+    '#description' => t("These settings map the originating OPAC's name (defined in the !sourceslink tab) into a taxonomy vocabulary.", array('!sourceslink' => l(t('sources'), 'admin/settings/millennium'))),
+  );
+  $form['opacname']['millennium_opacname_vid'] = array(
+    '#type' => 'select',
+    '#title' => t('Map originating OPAC name into'),
+    '#options' => $v_options,
+      '#default_value' => variable_get('millennium_opacname_vid', -1)
+  );
+
   $form['availability'] = array(
-    '#prefix' => '</table>',
     '#type' => 'fieldset',
     '#title' => t("Holdings locations to taxonomy mapping"),
     '#collapsible' => false,
@@ -322,20 +498,14 @@
   if ($tot_queued_items > 0) {
     $form['source']['#options']['queued'] = t('All records awaiting cron (@num items). <em>Note: this will empty the cron queue!</em>', array("@num" => $tot_queued_items));
   }
-  $form['millennium_webopac_baseurl'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Base URL for WebOPAC'),
-    '#description' => t('For example: http://example.com/'),
-    '#default_value' => variable_get('millennium_import_form_baseurl', variable_get('millennium_webopac_baseurl', '')),
-    '#prefix' => '<table id="baseurl-table"><tbody><tr><td>',
-    '#suffix' => '</td><td id="checkurl"></td></tr></tbody></table>',
-    '#ahah' => array(
-      'path' => 'millennium_checkurl_js',
-      'event' => 'change',
-      'wrapper' => 'checkurl',
-      'method' => 'replace',
-      'effect' => 'fade',
-    ),
+  $form['millennium_import_form_baseurl'] = array(
+    '#prefix' => '<div id="baseurl-table">',
+    '#suffix' => '</div>',
+    '#type' => 'select',
+    '#title' => t('Source WebOPAC'),
+    '#description' => t('Select one of the enabled sources.'),
+    '#default_value' => variable_get('millennium_import_form_baseurl', ''),
+    '#options' => drupal_map_assoc(array_merge(array(0 => t('<none>')), array_keys(variable_get('millennium_sources', array())))),
   );
   $form['list'] = array(
     '#type' => 'fieldset',
@@ -458,9 +628,9 @@
   if ($form_state["values"]["source"] == "range" ||
     $form_state["values"]["source"] == "list" ||
     $form_state["values"]["source"] == "query") {
-    $base_url = $form_state['values']['millennium_webopac_baseurl'];
+    $base_url = millennium_get_real_baseurl($form_state['values']['millennium_import_form_baseurl']);
     if (!$base_url) {
-      form_set_error('millennium_webopac_baseurl', t("Not a valid base URL."));
+      form_set_error('millennium_import_form_baseurl', t("Not a valid base URL."));
     }
   }
   if ($form_state["values"]["source"] == "range") {
@@ -537,7 +707,7 @@
 
     // Store some values for later
     variable_set('millennium_import_form_source', $form_state["values"]["source"]);
-    variable_set('millennium_import_form_baseurl', $form_state["values"]["millennium_webopac_baseurl"]);
+    variable_set('millennium_import_form_baseurl', $form_state["values"]["millennium_import_form_baseurl"]);
     variable_set('millennium_import_form_force_update', $form_state["values"]["force_update"]);
     variable_set('millennium_list_form_checkdigit', $form_state["values"]["ignore_checkdigit"]);
     variable_set('millennium_range_form_type', $form_state["values"]["type"]);
@@ -546,6 +716,8 @@
     variable_set('millennium_import_form_kw', $form_state["values"]["kw"]);
     variable_set('millennium_import_form_url', $form_state["values"]["url"]);
 
+    $base_url = millennium_get_real_baseurl($form_state['values']['millennium_import_form_baseurl']);
+
     if ($form_state["clicked_button"]["#value"] == t("Process these records during cron runs")) {
 
       // When? Cron runs
@@ -570,7 +742,7 @@
               $recnum,
               $form_state['values']['priority'],
               $form_state['values']['force_update'],
-              $form_state['values']['millennium_webopac_baseurl']);
+              $base_url);
             if ($ok)
               $queued++;
           }
@@ -587,7 +759,7 @@
             $type . $num,
             $form_state['values']['priority'],
             $form_state['values']['force_update'],
-            $form_state['values']['millennium_webopac_baseurl']);
+            $base_url);
           if ($ok) {
             $queued++;
           }
@@ -631,7 +803,7 @@
             if ($form_state["values"]["ignore_checkdigit"] == true) {
               $recnum = drupal_substr($recnum, 0, drupal_strlen($recnum)-1);
             }
-            $records_to_crawl[$form_state['values']['millennium_webopac_baseurl']][$recnum] = $recnum;
+            $records_to_crawl[$base_url][$recnum] = $recnum;
           }
         }
       }
@@ -641,7 +813,6 @@
         $type = $form_state["values"]["type"];
         $start = $form_state["values"]["start"];
         $end = $form_state["values"]["end"];
-        $base_url = millennium_get_real_baseurl($form_state['values']['millennium_webopac_baseurl']);
         for ($num = $start; $num <= $end; $num++) {
           $records_to_crawl[$base_url]["{$type}{$num}"] = "{$type}{$num}";
         }
@@ -650,7 +821,6 @@
       if ($form_state["values"]["source"] == "query") {
         require_once 'millennium.import.inc';
         variable_set('millennium_import_form_num_items', $form_state["values"]["num_items"]);
-        $base_url = $form_state['values']['millennium_webopac_baseurl'];
         $records = millennium_query_scrape(
           $form_state["values"]["kw"],
           $base_url,
Index: contributions/modules/millennium/millennium.cron.inc
--- contributions/modules/millennium/millennium.cron.inc Base (1.1.2.9)
+++ contributions/modules/millennium/millennium.cron.inc Locally Modified (Based On 1.1.2.9)
@@ -11,11 +11,11 @@
   $start_time = microtime(true);
 
   // Is URL set?
-  if (($base_url = millennium_get_real_baseurl()) === false) {
+  if (($base_url = variable_get('millennium_crawl_baseurl', false)) === false) {
     return;
   }
   if (!millennium_ping($base_url)) {
-    watchdog("Millennium", "Cron run aborted because WebOPAC at @url not responding", array("@url" => $base_url));
+    watchdog("Millennium", "Cron run aborted because WebOPAC at @url not responding", array("@url" => $base_url), WATCHDOG_ERROR);
     return;
   }
 
@@ -143,7 +143,7 @@
     // Chunk array into groups of 50 (maximum number allowed by bookcart)
     $recnums_chunks = array_chunk($recnums, 50);
     foreach ($recnums_chunks as $recnums_chunk) {
-      $fetched = millennium_fetch_records_via_bookcart($recnums_chunk);
+      $fetched = millennium_fetch_records_via_bookcart($recnums_chunk, false, $base_url);
       #dpm($fetched);
       foreach ($fetched['found'] as $data) {
         $result = millennium_process_record($data);
@@ -206,8 +206,8 @@
     $elapsed_time = microtime(true) - $start_time;
     millennium_time_history($elapsed_time, $tot_attempted, $tot_notfound, $tot_imported, $tot_fail);
 
-    watchdog("Millennium", "Cron import finished: @attempted attempted, @notfound not found on WebOpac, @imported imported ok, @failed could not import, in @time seconds (@time2 items per second)",
-      array("@attempted" => $tot_attempted, "@notfound" => $tot_notfound, "@imported" => $tot_imported, "@failed" => $tot_fail+0, "@time" => sprintf("%2.1f", $elapsed_time), "@time2" => sprintf("%2.1f", $tot_attempted / $elapsed_time))
\ No newline at end of file
+    watchdog("Millennium", "Cron import finished: @url: @attempted attempted, @notfound not found on WebOpac, @imported imported ok, @failed could not import, in @time seconds (@time2 items per second)",
+      array("@url" => $base_url, "@attempted" => $tot_attempted, "@notfound" => $tot_notfound, "@imported" => $tot_imported, "@failed" => $tot_fail+0, "@time" => sprintf("%2.1f", $elapsed_time), "@time2" => sprintf("%2.1f", $tot_attempted / $elapsed_time))
\ No newline at end of file
     );
   }
 }
Index: contributions/modules/millennium/millennium.import.inc
--- contributions/modules/millennium/millennium.import.inc Base (1.1.2.20)
+++ contributions/modules/millennium/millennium.import.inc Locally Modified (Based On 1.1.2.20)
@@ -71,12 +71,7 @@
  * @param $limit
  *   Maximum number of records to return.
  */
-function millennium_query_scrape($query, $base_url = false, $limit = 100) {
-  if ($base_url == false) {
-    $base_url = millennium_get_real_baseurl();
-  } else {
-    $base_url = millennium_get_real_baseurl($base_url);
-  }
+function millennium_query_scrape($query, $base_url, $limit = 100) {
   // Build query URL
   $url = "{$base_url}/search/X?" . drupal_urlencode($query);
   // Get records
@@ -90,7 +85,7 @@
  * @param $base_url
  *   The base url of the WebOPAC to process.
  */
-function millennium_mass_fetch($recnums, $base_url = false) {
+function millennium_mass_fetch($recnums, $base_url) {
   // This seems to be the limit for WebOPACs, do not change =)
   $max_records_for_bookcart = 40;
 
@@ -163,19 +158,13 @@
  *    'not_found' => array('i100000', 'i100005', [...])
  *  );
  */
-function millennium_fetch_records_via_bookcart($recnums, $complete_holdings = false, $base_url = false) {
+function millennium_fetch_records_via_bookcart($recnums, $complete_holdings = false, $base_url) {
   // Set the following to true for lots and lots of debugging info =)
   $debug = false;
 
   // Start timer to measure average performance
   timer_start("millennium_fetch_records_via_bookcart");
 
-  if ($base_url == false) {
-    $base_url = millennium_get_real_baseurl();
-  } else {
-    $base_url = millennium_get_real_baseurl($base_url);
-  }
-  
   $found_items = array();
   $not_found_items = array();
 
@@ -344,11 +333,11 @@
       // volume/copy" button? Do we want to check for complete holdings?
       if ($match[3] != "" && $complete_holdings == true) {
         // Yes. Issue additional request to OPAC to get holdings information for item.
-        $found_items[$current_recnum]['holdings'] = millennium_get_holdings_info($this_bib_recnum, null, $base_url);
+        $found_items[$current_recnum]['holdings'] = millennium_get_holdings_info($this_bib_recnum, $base_url);
       } else { 
         // No. Convert whatever holdings info (might be incomplete) we got along 
         // with the MARC export and add it to the item array.
-        $found_items[$current_recnum]['holdings'] = millennium_get_holdings_info($this_bib_recnum, $match[2], $base_url);
+        $found_items[$current_recnum]['holdings'] = millennium_get_holdings_info($this_bib_recnum, $base_url, $match[2]);
       }
       $found_items_index++;
     }
@@ -366,7 +355,7 @@
 
   // Grab XRECORD for item
   #foreach ($found_items as $item_recnum => $dummy) {
-  #  $result = millennium_fetch_recordpage($item_recnum, 'xml', $base_url);
+  #  $result = millennium_fetch_recordpage($item_recnum, $base_url, 'xml');
   #  if ($result->data) {
   #    $found_items[$item_recnum]['item_xml'] = $result->data;
   #  }
@@ -417,7 +406,7 @@
 function _millennium_confirm_found_not_found($base_url, $found_items, $not_found_items) {
   $false_positives = array();
   foreach ($found_items as $recnum => $found_item) {
-    $tmp = millennium_fetch_recordpage($recnum, 'plain', $base_url);
+    $tmp = millennium_fetch_recordpage($recnum, $base_url, 'plain');
     if (!strpos($tmp->data, '<a id="recordnum"')) {
       $false_positives[] = $recnum;
     }
@@ -432,7 +421,7 @@
 
   $false_negatives = array();
   foreach ($not_found_items as $recnum => $not_found_item) {
-    $tmp = millennium_fetch_recordpage($recnum, 'plain', $base_url);
\ No newline at end of file
+    $tmp = millennium_fetch_recordpage($recnum, $base_url, 'plain');
\ No newline at end of file
     if (strpos($tmp->data, '<a id="recordnum"')) {
       $false_negatives[] = $recnum;
     }
Index: contributions/modules/millennium/millennium.install
--- contributions/modules/millennium/millennium.install Base (1.1.2.5.2.1.2.10)
+++ contributions/modules/millennium/millennium.install Locally Modified (Based On 1.1.2.5.2.1.2.10)
@@ -23,6 +23,8 @@
   if ($phase == 'runtime') {
     $msg = array();
 
+    /* TODO: Decide if we will still check if a single OPAC (or multiple OPACs?)
+     * are responding.
     $base_url = millennium_get_real_baseurl();
     if (!$base_url) {
       $msg[] = $t("You must configure a base URL; please go to the !configurationpage", array("!configurationpage" => l($t("configuration page"), MILLENNIUM_SETTINGS_PATH)));
@@ -44,6 +46,7 @@
       'description' => $description,
       'severity' => $severity,
     );
+    */
   }
   return $requirements;
 }
@@ -261,8 +264,10 @@
   db_query("DELETE FROM {variable} WHERE name LIKE 'millennium_marc_vid_%'");
 
   variable_del('millennium_action_on_last_item_deleted');
+  variable_del('millennium_sources');
   variable_del('millennium_crawl_flag');
   variable_del('millennium_crawl_type');
+  variable_del('millennium_crawl_baseurl');
   variable_del('millennium_preferred_locations');
   variable_del('millennium_coverimage_baseurl');
   variable_del('millennium_time_history');
@@ -273,6 +278,9 @@
   variable_del('millennium_googlebooks_covers');
   variable_del('millennium_googlebooks_widget');
   variable_del('millennium_availability_vid');
+  variable_del('millennium_map_available_only');
+  variable_del('millennium_availability_strings');
+  variable_del('millennium_opacname_vid');
 }
 
 /**
@@ -341,3 +349,23 @@
   );
   return $ret;
 }
+
+function millennium_update_6005() {
+  $ret = array();
+  $sources = variable_get('millennium_sources', array());
+  // Move millennium_webopac_baseurl variable into the new sources variable
+  $base_url = variable_get('millennium_webopac_baseurl', '');
+  if ($base_url && !isset($sources[$base_url])) {
+    $sources[$base_url] = array('name' => '');
+  }
+  // Add sources from all currently-imported records
+  $result = db_query("SELECT DISTINCT(base_url) FROM {millennium_node_bib}");
+  while ($data = db_fetch_object($result)) {
+    if (!isset($sources[$data->base_url])) {
+      $sources[$data->base_url] = array('name' => '');
+    }
+  }
+  // Update/create sources variable
+  variable_set('millennium_sources', $sources);
+  return $ret;
+}
\ No newline at end of file
Index: contributions/modules/millennium/millennium.module
--- contributions/modules/millennium/millennium.module Base (1.13.2.33.2.2.2.86)
+++ contributions/modules/millennium/millennium.module Locally Modified (Based On 1.13.2.33.2.2.2.86)
@@ -583,6 +583,16 @@
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'weight' => -10,
   );
+  $items[MILLENNIUM_SETTINGS_PATH . '/crawl'] = array(
+    'title' => 'Automatic crawl',
+    'description' => 'Configure automatic crawl settings.',
+    'type' => MENU_LOCAL_TASK,
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('millennium_admin_crawl'),
+    'access callback' => 'user_access',
+    'access arguments' => array('administer millennium'),
+    'file' => 'millennium.admin.inc',
+  );
   $items[MILLENNIUM_SETTINGS_PATH . '/map'] = array(
     'title' => 'Taxonomy mapping',
     'description' => 'Configure MARC import mapping settings.',
@@ -613,20 +623,17 @@
     'access arguments' => array('use millennium mass import/refresh'),
     'file' => 'millennium.admin.inc',
   );
-  $items['millennium/import/%/%'] = array(
+  $items['millennium/import'] = array(
     'title' => 'Millennium node import',
-    'description' => 'Specify a URL and record number to import',
     'page callback' => 'millennium_node_import',
-    'page arguments' => array(2,3),
     'access callback' => 'user_access',
     'access arguments' => array('import single millennium items'),
     'type' => MENU_CALLBACK,
     'file' => 'millennium.pages.inc',
   );
-  $items['millennium/preview/%'] = array(
+  $items['millennium/preview'] = array(
     'title' => 'Millennium node import preview',
     'page callback' => 'millennium_preview_import' ,
-    'page arguments' => array(2),
     'access callback' => 'user_access',
     'access arguments' => array('import single millennium items'),
     'type' => MENU_CALLBACK,
@@ -647,13 +654,6 @@
     'access arguments' => array('access content'),
     'file' => 'millennium.pages.inc',
   );
-  $items['millennium_checkurl_js'] = array(
-    'page callback' => 'millennium_checkurl_js',
-    'type' => MENU_CALLBACK,
-    'access arguments' => array('administer millennium'),
-    'file' => 'millennium.pages.inc',
-  );
-
   $items['node/%node/millennium'] = array(
     'title' => 'Millennium Tools',
     'description' => 'Millennium tools for node',
@@ -887,7 +887,7 @@
   }
 
   $base_url = db_result(db_query("SELECT base_url FROM {millennium_node_bib} WHERE nid=%d", $nid));
-  $hold_url = millennium_permalink($bib_recnum, "hold", $base_url);
+  $hold_url = millennium_permalink($bib_recnum, $base_url, "hold");
   $links["millennium_holds"] = array(
     'title' => t('Place hold or request delivery'),
     'href' => $hold_url,
@@ -1111,7 +1111,7 @@
   if ($data['base_url']) {
     $base_url = $data['base_url'];
   } else {
-    $base_url = millennium_get_real_baseurl();
+    return array("success" => false, "error" => "No base_url in item #" . $data['item_recnum']);
   }
   
   // Had we already imported this item record?
@@ -1135,7 +1135,7 @@
     // Item was not imported before.
     // If not given as the function's argument, get the item's bib record number.
     if ($bib_recnum == null) {
-      $result = millennium_fetch_recordpage($data['item_recnum'], 'plain', $base_url);
+      $result = millennium_fetch_recordpage($data['item_recnum'], $base_url, 'plain');
       $record_html = $result->data;
       $ok = preg_match('/\/record=(b[0-9]+)/si', $record_html, $matches);
       if ($ok) {
@@ -1184,7 +1184,7 @@
 function millennium_process_item_record_new_import($data, $nid) {
   // Link item to bib, node
   $record = array('nid' => $nid, 'bib_recnum' => $data['bib_recnum'], 'item_recnum' => $data['item_recnum'],
-        'updated' => date("Y-m-d H:i:s"), 'created' => date("Y-m-d H:i:s"), 'base_url' => millennium_get_real_baseurl());
+        'updated' => date("Y-m-d H:i:s"), 'created' => date("Y-m-d H:i:s"), 'base_url' => $data['base_url']);
   $result = drupal_write_record('millennium_bib_item', $record);
   if (!$result) {
     drupal_set_message("millennium_process_item_record_new_import: drupal_write_record('millennium_bib_item') returned false");
@@ -1208,7 +1208,7 @@
 
     if ($xml_disabled == false) {
       // Get the item's data
-      $result = millennium_fetch_recordpage($data['item_recnum'], "xml", $data['base_url']);
+      $result = millennium_fetch_recordpage($data['item_recnum'], $data['base_url'], 'xml');
       $bib_xml = $result->data;
       if (stripos($item_xml, "403 Forbidden")) {
         drupal_set_message(t("XML disabled in WebOPAC."));
@@ -1277,7 +1277,7 @@
   if ($data['base_url']) {
     $base_url = millennium_get_real_baseurl($data['base_url']);
   } else {
-    $base_url = millennium_get_real_baseurl();
+    return array("success" => false, "error" => "Could not get bib_recnum for bib #" . $data['bib_recnum']);
   }
 
   $bib_import_history = db_fetch_object(db_query("SELECT * FROM {millennium_node_bib} WHERE bib_recnum='%s' AND base_url = '%s'", $data['bib_recnum'], $base_url));
@@ -1313,7 +1313,7 @@
   }
 
   // Re-create a nodeobject from the MARC record
-  $new_node = millennium_record_to_nodeobject($data['bib_recnum'], $data['marc'], $base_url);
+  $new_node = millennium_record_to_nodeobject($data['bib_recnum'], $base_url, $data['marc']);
   if ($new_node->success === false) {
     #drupal_set_message("millennium_process_bib_record(): millennium_record_to_nodeobject() returned error ". $result["error"]);
     return array("success" => false, "error" => $new_node->error);
@@ -1373,7 +1373,7 @@
   if ($data['base_url']) {
     $base_url = $data['base_url'];
   } else {
-    $base_url = millennium_get_real_baseurl();
+    return;
   }
 
   // Get bibrecord number and erase any existing node-item relationships
@@ -1418,7 +1418,7 @@
   if ($data['base_url']) {
     $base_url = $data['base_url'];
   } else {
-    $base_url = millennium_get_real_baseurl();
+    return;
   }
   
   db_query("DELETE FROM {millennium_bib_item} WHERE bib_recnum='%s' AND base_url = '%s'", $data['bib_recnum'], $base_url);
@@ -1448,9 +1448,8 @@
  * @param recnum Millennium record number (b123456, i123456) to fetch
  * @param string $marc_text An optional MARC record to import from.
  */
-function millennium_record_to_nodeobject($recnum, $marc_text = null, $base_url = false) {
+function millennium_record_to_nodeobject($recnum, $base_url, $marc_text = null) {
   $result = new stdClass();
-  $base_url = millennium_get_real_baseurl($base_url);
   if ($marc_text == null) {
     $marc_text = millennium_fetch_marc($recnum, $base_url);
     if (!$marc_text) {
@@ -1467,7 +1466,7 @@
   }
   
   // Add Millennium-only info
-  $biblio["url"] = l(t('Link to original record'), millennium_permalink($recnum, "plain", $base_url));
+  $biblio["url"] = l(t('Link to original record'), millennium_permalink($recnum, $base_url, 'plain'));
   $biblio["recnum"] = $recnum;
   $biblio["base_url"] = $base_url;
   // If no LCC, try to get from item holdings
@@ -1522,12 +1521,15 @@
   // Return the node
   $nodeobject = (object)$node;
 
-  // Add taxonomy
+  // Add marc info to node's taxonomy
   millennium_add_taxonomy_to_node($nodeobject, $parsed_marc);
 
-  // Add availability info
+  // Add availability info to node's taxonomy
   millennium_add_availability_to_node($nodeobject, $recnum, $base_url);
 
+  // Add OPAC name to node's taxonomy
+  millennium_add_taxonomy_opacname_to_node($nodeobject, $base_url);
+
   if (!$nodeobject) {
     $result->success = false;
     $result->error = "Could not create node object from parsed MARC for $recnum";
@@ -1540,14 +1542,14 @@
 /**
  * Returns the user-defined base URL for the Millennium server (e.g. http://example.com/)
  */
-function millennium_get_real_baseurl($base_url = false) {
-  if ($base_url === false) {
-    $base_url = variable_get('millennium_webopac_baseurl', '');
+function millennium_get_real_baseurl($base_url) {
+  if ($base_url === false || $base_url == '') {
+    return false;
   }
-  if ($base_url == '') {
+  $parts = @parse_url(trim($base_url));
+  if ($parts['scheme'] != 'http' && $parts['scheme'] != 'https') {
     return false;
   }
-  $parts = @parse_url($base_url);
   if (!$parts['host']) {
     return false;
   }
@@ -1555,7 +1557,7 @@
   if (!empty($parts['port'])) {
     $hostname .= ":" . $parts['port'];
   }
-  return "http://$hostname";
+  return $parts['scheme'] . "://$hostname";
 }
 
 /**
@@ -1563,7 +1565,7 @@
  * @param recnum Millennium record number (b123456, i123456) to fetch
  * @param mode String specifying type of view: plain, marc, items, xml
  */
-function millennium_fetch_recordpage($recnum, $mode = "plain", $base_url = false) {
+function millennium_fetch_recordpage($recnum, $base_url, $mode = 'plain') {
   global $millennium_fetch_recordpage_cache;
   $key = $recnum . $mode . $base_url;
   #drupal_set_message("millennium_fetch_recordpage($recnum, $mode): key = $key");
@@ -1573,7 +1575,7 @@
   }
 
   // Get URL to fetch
-  $url = millennium_permalink($recnum, $mode, $base_url);
+  $url = millennium_permalink($recnum, $base_url, $mode);
   if ($url == false) {
     return false;
   }
@@ -1590,15 +1592,7 @@
 /**
  * Returns a URL for a certain item record view in Millennium
  */
-function millennium_permalink($recnum, $mode = 'plain', $base_url = false) {
-  if ($base_url === false) {
-    $base_url = millennium_get_real_baseurl();
-  }
-  if ($base_url == false) {
-    return false;
-  } else {
-    $base_url = millennium_get_real_baseurl($base_url);
-  }
+function millennium_permalink($recnum, $base_url, $mode = 'plain') {
   // Determine if record is for an item or bib
   $recnum_type = substr($recnum, 0, 1);
 
@@ -1613,7 +1607,7 @@
     case "hold":
     case "items":
       if ($recnum_type == "i") {
-        $bib_recnum = db_result(db_query("SELECT bib_recnum FROM {millennium_bib_item} WHERE item_recnum='%s' AND base_url = '%s'", $recnum, millennium_get_real_baseurl()));
+        $bib_recnum = db_result(db_query("SELECT bib_recnum FROM {millennium_bib_item} WHERE item_recnum='%s' AND base_url = '%s'", $recnum, $base_url));
         $item_recnum = $recnum;
       } else {
         $bib_recnum = $recnum;
@@ -1641,7 +1635,7 @@
  * @param bib_recnum Millennium record number (b123456) to fetch
  */
 function millennium_fetch_marc($bib_recnum, $base_url) {
-  $result = millennium_fetch_recordpage($bib_recnum, "marc", $base_url);
+  $result = millennium_fetch_recordpage($bib_recnum, $base_url, "marc");
   if (! $result->data) {
     return false;
   }
@@ -1911,7 +1905,7 @@
     return;
   }
   $locations_available = array();
-  $holdings = millennium_get_holdings_info($recnum, null, $base_url);
+  $holdings = millennium_get_holdings_info($recnum, $base_url);
   foreach ($holdings as $item) {
     if (
       !variable_get('millennium_map_available_only', FALSE)
@@ -1927,6 +1921,16 @@
   millennium_add_node_taxonomy_terms($nodeobject, variable_get('millennium_availability_vid', -1) , $location_names);
 }
 
+function millennium_add_taxonomy_opacname_to_node(&$nodeobject, $base_url) {
+  if (variable_get('millennium_opacname_vid', -1) == -1) {
+    return;
+  }
+  $sources = variable_get('millennium_sources', array());
+  if (isset($sources[$base_url]) && $sources[$base_url]['name']) {
+    millennium_add_node_taxonomy_terms($nodeobject, variable_get('millennium_opacname_vid', -1) , array($sources[$base_url]['name']));
+  }
+}
+
 /**
  * Helper function that eliminates trailing (nonessential) characters from MARC data
  */
@@ -2107,9 +2111,9 @@
   // Load vocabularies
   if ($vocabs_vid_nodetype === FALSE) {
     $vocabs = taxonomy_get_vocabularies();
-    foreach ($vocabs as $vid => $vocab) {
-      foreach ($vocab->nodes as $type) {
-        $vocabs_vid_nodetype[$vid][$type] = TRUE;
+    foreach ($vocabs as $loop_vid => $loop_vocab) {
+      foreach ($loop_vocab->nodes as $type) {
+        $vocabs_vid_nodetype[$loop_vid][$type] = TRUE;
       }
     }
   }
@@ -2132,8 +2136,6 @@
     return;
   }
 
-  $nid = $nodeobject->nid;
-
   if (isset($nodeobject->taxonomy["tags"][$vid])) {
     $tags_this_vid = $nodeobject->taxonomy["tags"][$vid];
   } else {
@@ -2413,7 +2415,7 @@
       
       if ($base_url) {
         $output .= "<div class='millennium holdings load' id='nid-$nid'>";
-        $output .= l( $msg, millennium_permalink($bib_recnum, "plain", $base_url));
+        $output .= l( $msg, millennium_permalink($bib_recnum, $base_url, 'plain'));
         $output .= "</div>";
       }
       break;
@@ -2425,7 +2427,7 @@
 
     case "recordlink":
       $bib_recnum = millennium_bib_recnum_from_node($nid);
-      $record_link = l( $msg, millennium_permalink($bib_recnum, "plain", $base_url));
+      $record_link = l( $msg, millennium_permalink($bib_recnum, $base_url, 'plain'));
       $output .= "<div class='millennium holdings'>". $record_link ."</div>";
       break;
   }
@@ -2442,13 +2444,10 @@
  * @return array
  *   A holdings array.
  */
-function millennium_get_holdings_info($recnum, $fetched_html = null, $base_url = false) {
+function millennium_get_holdings_info($recnum, $base_url, $fetched_html = null) {
   static $cache;
   
   $items = array();
-  if ($base_url === false) {
-    $base_url = millennium_get_real_baseurl();
-  }
   
   if ($fetched_html == null) {
     // Build a cache id
@@ -2465,7 +2464,7 @@
       return $cache[$cid]->data;
     }
     // No cache, fetch the item information from WebOPAC
-    $result = millennium_fetch_recordpage($recnum, "items", $base_url);
+    $result = millennium_fetch_recordpage($recnum, $base_url, "items");
     if (! $result->data) {
       return false;
     }
@@ -2815,3 +2814,22 @@
 
   return $result;
 }
+
+/**
+ * Returns a sources array populated with an extra 'node_count' element per each
+ * source.
+ * 
+ * @return array
+ *   A sources array derived from the millennium_sources Drupal variable
+ */
+function millennium_get_sources_count() {
+  $sources = variable_get("millennium_sources", array());
+  if (sizeof($sources) > 0) {
+    // Build a counting query
+    $result = db_query('SELECT count(1) n, base_url FROM {millennium_node_bib} GROUP BY base_url');
+    while ($data = db_fetch_object($result)) {
+      $sources[$data->base_url]['node_count'] = $data->n;
+    }
+  }
+  return $sources;
+}
\ No newline at end of file
Index: contributions/modules/millennium/millennium.pages.inc
--- contributions/modules/millennium/millennium.pages.inc Base (1.1.2.5)
+++ contributions/modules/millennium/millennium.pages.inc Locally Modified (Based On 1.1.2.5)
@@ -9,21 +9,19 @@
 /**
  * Callback function from hook_menu() that imports a new node based on a given item or bib record number.
  */
-function millennium_node_import($base_url, $recnum) {
-  // Check the base_url
-  if (strpos($base_url, "http") === false) {
-    $base_url = "http://" . $base_url;
+function millennium_node_import() {
+  // Check if URL is permalink
+  $url = check_plain($_GET['url']);
+  if (preg_match('/^http[s]*:\/\/[^\/]+\/record=([bi][0-9]+)/', $url, $matches)) {
+    $recnum = $matches[1];
+    $base_url = millennium_get_real_baseurl($url);
   }
-  $base_url = millennium_get_real_baseurl($base_url);
-  if (!$base_url) {
-    drupal_set_message(t("Incorrect base url given."), "error");
-    drupal_goto("<front>");
-    return;
+  else {
+    return t('Argument should be a valid record permalink URL with syntax: http://example.com/record=b123456');
   }
     
   if (!preg_match(MILLENNIUM_RECORD_PREG_NOCHECKDIGIT, $recnum)) {
-    drupal_set_message(t("@recnum is not a valid Millennium record number (b123456, i123456).", array('@recnum' => $recnum)), "error");
-    drupal_goto("<front>");
+    return t("@recnum is not a valid Millennium record number (b123456, i123456).", array('@recnum' => $recnum));
   }
 
   $data = array('base_url' => $base_url);
@@ -38,8 +36,7 @@
   }
 
   if ($result["success"] == false) {
-    drupal_set_message(t("Failed to import @recnum from URL @url: @error", array('@recnum' => $recnum, '@url' => $base_url, "@error" => $result["error"])));
-    drupal_goto("<front>");
+    return t("Failed to import @recnum from URL @url: @error", array('@recnum' => $recnum, '@url' => $base_url, "@error" => $result["error"]));
   } else {
     drupal_set_message(t("Record @recnum successfully imported from @url.", array("@recnum" => $recnum, '@url' => $base_url)));
     drupal_goto('node/' . $result['node']->nid);
@@ -49,10 +46,24 @@
 /**
  * Callback function from hook_menu to show how an millennium record would be imported
  */
-function millennium_preview_import($recnum) {
-  $output = t("You can also !link", array('!link' => l(t("import this record now"), "millennium/import/$recnum")));
+function millennium_preview_import() {
+  // Check if URL is permalink
+  $url = check_plain($_GET['url']);
+  if (preg_match('/^http[s]*:\/\/[^\/]+\/record=([bi][0-9]+)/', $url, $matches)) {
+    $recnum = $matches[1];
+    $base_url = millennium_get_real_baseurl($url);
+
+    if (!preg_match(MILLENNIUM_RECORD_PREG_NOCHECKDIGIT, $recnum)) {
+      return t("@recnum is not a valid Millennium record number (b123456, i123456).", array('@recnum' => $recnum));
+    }
+    
+    $output = t("You can also !link", array('!link' => l(t("import this record now"), "millennium/import", array('query' => "url=$url"))));
   $output .= "<hr />";
-  $output .= _millennium_show_conversion($recnum);
+    $output .= _millennium_show_conversion($recnum, $base_url);
+  }
+  else {
+    $output = t('Argument should be a valid record permalink URL with syntax: http://example.com/record=b123456');
+  }
   return $output;
 }
 
@@ -72,7 +83,7 @@
   $output = "<h2>". t("Import history") . "</h2>";
 
   $items[] = t("WebOPAC: !link", array("!link" => l($data->base_url, $data->base_url)));
-  $items[] = t("Bibliographic record #: !link", array("!link" => l($bib_recnum, millennium_permalink($bib_recnum, "plain", $data->base_url))));
+  $items[] = t("Bibliographic record #: !link", array("!link" => l($bib_recnum, millennium_permalink($bib_recnum, $data->base_url, 'plain'))));
   $recnums = millennium_item_recnums_from_node($node->nid);
   if ($recnums) {
     $items[] = t("Item records: @items", array("@items" => implode(", ", $recnums)));
@@ -84,7 +95,7 @@
 
   $output .= "<p></p>";
   $output .= "<h2>". t('MARC record and conversion') ."</h2>";
-  $output .= _millennium_show_conversion($bib_recnum, $node->millennium_biblio_data["marc"], $data->base_url);
+  $output .= _millennium_show_conversion($bib_recnum, $data->base_url, $node->millennium_biblio_data["marc"]);
   return $output;
 }
 
@@ -139,7 +150,7 @@
   else {
     $bib_recnum = millennium_bib_recnum_from_node($nid);
     $base_url = db_result(db_query("SELECT base_url FROM {millennium_node_bib} WHERE nid=%d", $nid));
-    $holdings = millennium_get_holdings_info($bib_recnum, NULL, $base_url);
+    $holdings = millennium_get_holdings_info($bib_recnum, $base_url);
     $output = theme('millennium_holdings', $nid, $holdings, $page);
     cache_set($cid, serialize($output), 'cache', time() + 900); // 15 minutes
   }
@@ -232,7 +243,7 @@
     $chart[$name] = "<img src='$chart_url'>";
     $max_execution_time = ini_get('max_execution_time');
     if ($max_time > $max_execution_time * 0.9) {
-      $warning = t('<strong>Warning</strong>: the time spent processing records in one or more recent cron runs exceeds the maximum time allowed for PHP scripts.<p>To fix this, you can take either or both of these steps:<ul><li>increase <tt>max_execution_time</tt> in <tt>settings.php</tt> in your Drupal installation (current setting: @time seconds)<li>reduce the amount of records to process during cron runs in the !configurationpage.</ul>', array('@time' => $max_execution_time, '!configurationpage' => l(t('configuration page'), MILLENNIUM_SETTINGS_PATH)));
+      $warning = t('<strong>Warning</strong>: the time spent processing records in one or more recent cron runs exceeds the maximum time allowed for PHP scripts.<p>To fix this, you can take either or both of these steps:<ul><li>increase <tt>max_execution_time</tt> in <tt>settings.php</tt> in your Drupal installation (current setting: @time seconds)<li>reduce the amount of records to process during cron runs in the !configurationpage.</ul>', array('@time' => $max_execution_time, '!configurationpage' => l(t('configuration page'), MILLENNIUM_SETTINGS_PATH . '/crawl')));
       drupal_set_message($warning, 'warning');
       #$chart[$name] .= "<div class=warning>";
       #$chart[$name] .=
@@ -263,7 +274,7 @@
 /**
  * Diagnostics to show conversion of MARC into fields for node object
  */
-function _millennium_show_conversion($recnum, $marc_text = false, $base_url = false) {
+function _millennium_show_conversion($recnum, $base_url, $marc_text = false) {
   $output = "";
   $base_url = millennium_get_real_baseurl($base_url);
   if ($base_url == false) {
@@ -277,7 +288,7 @@
     $marc_text_msg = t('This MARC record was just fetched from the WebOPAC.');
   }
 
-  $tmp_node = millennium_record_to_nodeobject($recnum, $marc_text, $base_url);
+  $tmp_node = millennium_record_to_nodeobject($recnum, $base_url, $marc_text);
   if (!$tmp_node || !$tmp_node->millennium_biblio_data) {
     return t("Millennium data in node has errors and can not be displayed.");
   }
@@ -304,7 +315,7 @@
   $output .= "<pre>" . var_export($tmp, TRUE) . "</pre>";
 
   $output .= "<h4>" .t('Taxonomy') ."</h4>";
-  $output .= t("You can change MARC-to-taxonomy mappings at the !configurationpage", array("!configurationpage" => l(t('configuration page'), MILLENNIUM_SETTINGS_PATH)));
+  $output .= t("You can change MARC-to-taxonomy mappings at the !configurationpage", array("!configurationpage" => l(t('configuration page'), MILLENNIUM_SETTINGS_PATH . '/map')));
   $output .= "<pre>". var_export($tmp_node->taxonomy, true) ."</pre>";
 
   return $output;
@@ -313,9 +324,9 @@
 /**
  * Callback function for URL checker
  */
-function millennium_checkurl_js() {
+function millennium_checkurl_js($var_name = false) {
   $form_state = array('values' => $_POST);
-  $base_url = millennium_get_real_baseurl($form_state['values']['millennium_webopac_baseurl']);
\ No newline at end of file
+  $base_url = millennium_get_real_baseurl($form_state['values'][check_plain($var_name)]);
   $ok = millennium_ping($base_url);
   $output = $ok ? t("WebOPAC is responding normally.") : t("Not a valid WebOPAC.");
   $class = $ok ? "status" : "error";
Index: contributions/modules/millennium/millennium.theme.inc
--- contributions/modules/millennium/millennium.theme.inc Base (1.1.2.3)
+++ contributions/modules/millennium/millennium.theme.inc Locally Modified (Based On 1.1.2.3)
@@ -12,6 +12,8 @@
 function theme_millennium_holdings($nid, $holdings, $page = true) {
   static $re;
 
+  $biblio_data = millennium_get_biblio_data($nid);
+
   // Preferred locations
   if (empty($re)) {
     $tmp = variable_get('millennium_preferred_locations', '');
@@ -61,9 +63,11 @@
         }
       }
 
-      // Style differently
+      // Style differently if items are "available" according to status and
+      // millennium_availability_strings variable
       $class = "notavailable";
-      if ($item['status'] == "DISPONIBLE" || $item['status'] == "AVAILABLE") {
+      $availability_strings = explode(',', variable_get('millennium_availability_strings', MILLENNIUM_ITEM_AVAILABLE_STRINGS));
+      if (in_array($item['status'], $availability_strings)) {
         $class = "available";
       }
       $shown ++;
@@ -74,10 +78,20 @@
       if ($item['copy']) {
         $classnumber_volume_copy .= ($classnumber_volume_copy ? " " : "") . $item['copy'];
       }
+      // Location
+      $location = $item['location'];
+      if (variable_get('millennium_holdings_opacname', 1)) {
+        // Add opacname prefix to $location
+        $sources = variable_get('millennium_sources', array());
+        $source = $sources[$biblio_data["base_url"]];
+        if ($source && $source['name']) {
+          $location = $source['name'] . ": " . $location;
+        }
+      }
       $rows[] = array(
         'data' => array(
           array('data' => $item['status']),
-          array('data' => $item['location']),
\ No newline at end of file
+          array('data' => $location),
\ No newline at end of file
           array('data' => $classnumber_volume_copy),
           //array('data' => $item['message']),
         ),
Index: contributions/modules/millennium/millennium_auth.module
--- contributions/modules/millennium/millennium_auth.module Base (1.1.2.6)
+++ contributions/modules/millennium/millennium_auth.module Locally Modified (Based On 1.1.2.6)
@@ -2,6 +2,8 @@
 // $Id: millennium_auth.module,v 1.1.2.6 2009/10/30 21:47:16 janusman Exp $
 
 module_load_include('inc', 'millennium_auth', 'patroninfo');
+define('MILLENNIUM_AUTH_DEFAULT_PIN_MSG', 'The PIN used for renewals and other library services.');
+define('MILLENNIUM_AUTH_DEFAULT_ID_MSG', 'Enter your library card barcode.');
  
 /**
  * Implementation of hook_help
@@ -14,6 +16,53 @@
 }
 
 /**
+ * Implementation of hook_menu()
+ */
+function millennium_auth_menu() {
+  $items = array();
+  $items[MILLENNIUM_SETTINGS_PATH . '/authentication'] = array(
+    'title' => 'Authentication',
+    'description' => 'Configure authentication settings.',
+    'type' => MENU_LOCAL_TASK,
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('millennium_auth_settings_form'),
+    'access callback' => 'user_access',
+    'access arguments' => array('administer millennium'),
+  );
+  return $items;
+}
+
+function millennium_auth_settings_form() {
+  $form = array();
+  $form['millennium_auth_default_base_url'] = array(
+    '#type' => 'select',
+    '#title' => t('WebOPAC source to use for authentication'),
+    '#description' => t('Select one of the enabled sources.'),
+    '#default_value' => variable_get('millennium_auth_default_base_url', ''),
+    '#options' => drupal_map_assoc(array_merge(array(0 => t('<none>')), array_keys(variable_get('millennium_sources', array())))),
+  );
+  $form['millennium_auth_use_https'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Force use of HTTPS for authentication requests'),
+    '#description' => t('Note: your OPAC must have the appropriate configuration in place for this to work.'),
+    '#default_value' => variable_get('millennium_auth_use_https', FALSE),
+  );
+  $form['login']['millennium_auth_id_message'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Library ID field help message'),
+    '#description' => t('This is shown below the login form.'),
+    '#default_value' => variable_get('millennium_auth_id_message', t(MILLENNIUM_AUTH_DEFAULT_ID_MSG)),
+  );
+  $form['login']['millennium_auth_pin_message'] = array(
+    '#type' => 'textfield',
+    '#title' => t('PIN field help message'),
+    '#description' => t('This is shown below the login form.'),
+    '#default_value' => variable_get('millennium_auth_pin_message', t(MILLENNIUM_AUTH_DEFAULT_PIN_MSG)),
+  );
+  return system_settings_form($form);
+}
+
+/**
  * Implementation of hook_user().
  */
 function millennium_auth_user($op, &$edit, &$account, $category = FALSE) {
@@ -51,7 +100,7 @@
         $list = array();
         if (is_array($patroninfo_data["checkouts"])) {
           $msg = theme("millennium_auth_checkouts", $patroninfo_data["checkouts"]);
-          $msg .= '<a href='. millennium_get_real_baseurl(). '/patroninfo>'. t('Renew your items') .'</a>';
+          $msg .= '<a href='. variable_get('millennium_auth_default_base_url', ''). '/patroninfo>'. t('Renew your items') .'</a>';
         } else {
           $msg = t('You have no checked out items.');
         }
@@ -123,7 +172,7 @@
           $millennium_auth_newuser['username'], 
           $millennium_auth_newuser['password']
         );
-        _millennium_auth_debug("millennium_auth_user($op....) : Saved new user's PIN and lastname");
+        _millennium_auth_debug("millennium_auth_user($op....) : Saved new user's PIN and last name");
         // Log new user
         watchdog(
           'millennium auth', 
@@ -160,12 +209,12 @@
   }
   foreach($items as $item) {
     // Has record been imported into Drupal?
-    $nid = db_result(db_query("SELECT nid FROM {millennium_bib_item} where item_recnum='%s' AND base_url = '%s'", $item["item_recnum"], millennium_get_real_baseurl()));
+    $nid = db_result(db_query("SELECT nid FROM {millennium_bib_item} where item_recnum='%s' AND base_url = '%s'", $item["item_recnum"], variable_get('millennium_auth_default_base_url', '')));
     if ($nid) {
       $link = l($item["title"], "node/$nid");
     }
     else {
-      $link = l($item["title"], millennium_get_real_baseurl() ."/record=". $item["item_recnum"]);
+      $link = l($item["title"], variable_get('millennium_auth_default_base_url', '') ."/record=". $item["item_recnum"]);
     }
     
     $item_html = $link ."<br />". t('Due date: @date', array("@date" => format_date($item["duedate_timestamp"], 'custom', 'D, m/d/Y'))); 
@@ -303,11 +352,22 @@
     return $patroninfo_data;
   } 
   
+  $base_url = variable_get('millennium_auth_default_base_url', '');
+  // Use HTTPs if settings indicate so. TODO make this automatic?
+  if (variable_get('millennium_auth_use_https', FALSE)) {
+    $base_url = str_replace("http://", "https://", $base_url);
+  }
+
   // Connect to Millennium and get the patron's information
-  $patroninfo_data = patroninfo_start_session(millennium_get_real_baseurl(), $username, $lastname, $pin);
+  $patroninfo_data = patroninfo_start_session($base_url, $username, $lastname, $pin);
   
   if ($patroninfo_data["success"] != true) {
   	_millennium_auth_debug("_millennium_auth_get_patroninfo_by_pindata(): Error: ". $patroninfo_data["errormsg"]);
+    watchdog('millennium auth',
+      'Connection error: @error',
+      array('@error' => $patroninfo_data["errormsg"]),
+      WATCHDOG_ERROR
+    );
   	return false;
 	}
 	
@@ -321,8 +381,13 @@
  * Implementation of hook_form_alter().
  */
 function millennium_auth_form_alter(&$form, &$form_state, $form_id) {
-  #drupal_set_message("millennium_auth_form_alter($form_id)");
+  $base_url = variable_get('millennium_auth_default_base_url', 0);
   
+  // Make sure that settings are in place.
+  if ($base_url != 0) {
+    return;
+  }
+
   // User edit form
   if ($form_id == 'user_edit') {
     // Prevent users from changing PIN (password)
@@ -332,7 +397,7 @@
       $form['account']['pass'] = array(
         '#title' => t('Change library PIN'),
         '#type' => 'item',
-        '#description' => l( t('Click on this link to change your library PIN'), millennium_get_real_baseurl() .'/patroninfo*eng'),
+        '#description' => l( t('Click on this link to change your library PIN'), "{$base_url}/patroninfo*eng"),
       );
     }
     
@@ -355,7 +420,7 @@
       '#type' => 'textfield',
       '#title' => t('Your Library ID'),
       '#size' => 10,
-      '#description' => t('Enter your library card barcode.'), // TODO: Make configurable!
+      '#description' => check_plain(variable_get('millennium_auth_id_message', t(MILLENNIUM_AUTH_DEFAULT_ID_MSG))),
       '#default_value' => '',
       '#attributes' => array('tabindex' => 2),
       '#required' => 1,
@@ -364,7 +429,7 @@
     
     $form['pass']['#title'] = t('Library PIN');
     $form['pass']['#size'] = 12;
-    $form['pass']['#description'] = t('The PIN used for renewals and other library services.');
+    $form['pass']['#description'] = check_plain(variable_get('millennium_auth_pin_message', t(MILLENNIUM_AUTH_DEFAULT_PIN_MSG)));
     $form['pass']['#attributes'] = array('tabindex' => 3);
     
     // Add tabindex to "submit" button
@@ -410,7 +475,7 @@
   
   if (!millennium_auth_authenticate($form_state['values'])) {
     // Authentication failed
-    form_set_error('name', t('Your lastname, library ID or library PIN are incorrect.'));
+    form_set_error('name', t('Your last name, library ID or library PIN are incorrect.'));
   }
   
 }
