# 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: millennium_auth.install
--- millennium_auth.install Base (1.1.2.3)
+++ millennium_auth.install Locally Modified (Based On 1.1.2.3)
@@ -15,13 +15,13 @@
         'not null' => TRUE,
       ),
       'lastname' => array(
-        'description' => t('User last name as needed by WebOPAC'),
+        'description' => t('User last name, if needed by WebOPAC'),
         'type' => 'char',
         'length' => 50,
         'not null' => FALSE,
       ),
       'name' => array(
-        //'description' => t('TODO'),
+        'description' => t('The user name. Initially this is the same as the Drupal username'),
         'type' => 'char',
         'length' => 50,
         'not null' => FALSE,
@@ -53,3 +53,16 @@
   variable_del('millennium_auth_id_message');
   variable_del('millennium_auth_pin_message');
 }
+
+/**
+ * Encrypt stored PINs
+ */
+function millennium_auth_update_6001() {
+  $result = db_query("SELECT * FROM {millennium_auth_users}");
+  while ($data = db_fetch_object($result)) {
+    $newpin = _millennium_auth_encrypt($data->pin);
+    $newlastame = _millennium_auth_encrypt($data->lastname);
+    db_query("UPDATE {millennium_auth_users} SET pin = '%s', lastname = '%s' WHERE uid = %d", $newpin, $newlastame, $data->uid);
+  }
+}
+
Index: millennium_auth.module
--- millennium_auth.module Base (1.1.2.8)
+++ millennium_auth.module Locally Modified (Based On 1.1.2.8)
@@ -4,6 +4,7 @@
 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.');
+define('MILLENNIUM_AUTH_KEY', 'myxtplyxed');
  
 /**
  * Implementation of hook_help
@@ -47,15 +48,33 @@
     '#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_login_fields'] = array(
+    '#type' => 'radios',
+    '#title' => t('Number of login input boxes configured on the Millennium system'),
+    '#options' => array(
+          '2' => t('2 boxes: e.g. name + password, barcode + password (or similar)'),
+          '3' => t('3 boxes: e.g. name + barcode + password, name + last name + password (or similar)'),
+        ),
+    '#default_value' => variable_get('millennium_auth_login_fields', '2'),
+  );
+  $form['login']['millennium_auth_date_format'] = array(
+    '#type' => 'radios',
+    '#title' => t('Date format for due dates shown on items on /patroninfo screens'),
+    '#options' => array(
+          'MDY' => t('Month-Day-Year (e.g. 01-25-11)'),
+          'DMY' => t('Day-Month-Year (e.g. 25-01-11)'),
+        ),
+    '#default_value' => variable_get('millennium_auth_date_format', 'MDY'),
+  );
   $form['login']['millennium_auth_id_message'] = array(
     '#type' => 'textfield',
-    '#title' => t('Library ID field help message'),
+    '#title' => t('Library ID field help message on login form'),
     '#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'),
+    '#title' => t('PIN field help message on login form'),
     '#description' => t('This is shown below the login form.'),
     '#default_value' => variable_get('millennium_auth_pin_message', t(MILLENNIUM_AUTH_DEFAULT_PIN_MSG)),
   );
@@ -73,47 +92,62 @@
     
   switch ($op) {
     case "login":
-    
       break;
-    case "form": 
-      
+    case "form":
+      if ($category == "account" || $category == "") {
+        $form['millennium'] = array(
+          '#type' => 'fieldset',
+          '#title' => t('Library account options'),
+        );
+        // Show an extra form element on user/XXX/edit that allows to opt-in
+        // to share user's checed-out items information.
+        $form['millennium']['millennium_auth_show_my_checkouts'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('I opt-in to share my list of current checked-out items'),
+          '#default_value' => $edit['millennium_auth_show_my_checkouts'],
+          '#description' => t('This will allow anyone with access to your profile to view your current check outs. Use with care.'),
+        );
+        return $form;
+      }
       break;
       
-    case "view":
-      #_millennium_auth_debug("millennium_auth_user($op): account == ".var_export($account, true));
-      
-      // Users can only view their OWN data
-      // TODO: Let users share their items, have RSS feeds, free beer ETC ETC!! =) =) =)
-      #if ($user->uid != $account ->uid) {
-      #  return;
-      #}
-      
+    case "view":   
+      // Users can only view their OWN data unless they have opted in to share
+      // ther information (see flag $user->millennium_auth_show_my_checkouts)
+      if (
+          (isset($account->millennium_auth_show_my_checkouts) && !$account->millennium_auth_show_my_checkouts)
+          && $user->uid != $account->uid
+          ) {
+        break;
+      }
+
       // Is this user registered with Millennium?
       $pincache = _millennium_auth_get_pincache($account->uid);
       if ($pincache == false) {
         break; // No. Goodbye, dave.
-      }      
-        
+      }            
       $patroninfo_data = _millennium_auth_get_patroninfo_by_uid($account->uid);
       if ($patroninfo_data) {
         #print_r($patroninfo_data);
         $list = array();
         if (is_array($patroninfo_data["checkouts"])) {
-          $msg = theme("millennium_auth_checkouts", $patroninfo_data["checkouts"]);
-          $msg .= '<a href='. variable_get('millennium_auth_default_base_url', ''). '/patroninfo>'. t('Renew your items') .'</a>';
+          $checked_out_msg = theme("millennium_auth_checkouts", $patroninfo_data["checkouts"]);
+          $checked_out_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.');
+          $checked_out_msg = t('You have no checked out items.');
         }
 
-        $msg2 = t('@num-holds holds, @num-ready ready for pickup', 
-        array(
-          '@num-holds' => intval($patroninfo_data["number_holds"]),
-          '@num-ready' => intval($patroninfo_data["number_holds_ready"])
-          ) );
+        $holds_msg = t(
+          '@num-holds holds, @num-ready ready for pickup.', 
+          array(
+            '@num-holds' => intval($patroninfo_data["number_holds"]),
+            '@num-ready' => intval($patroninfo_data["number_holds_ready"])
+          )
+        );
 
         $account->content['millennium'] = array(
          '#type' => 'user_profile_category',
-         '#title' => t("Library account status"),
+         '#title' => t('Library account status'),
          '#attributes' => array('class' => 'millennium items'),
          '#weight' => 1,
         );
@@ -121,7 +155,7 @@
         $account->content['millennium']['millennium_auth_holds'] = array(
           '#type' => 'user_profile_item',
           '#title' => t('Current holds:'),
-          '#value' => $msg2,
+          '#value' => $holds_msg,
           '#attributes' => array('class' => 'millennium items'),
         );
 
@@ -135,15 +169,14 @@
         $account->content['millennium']['millennium_auth_checkedout'] = array(
           '#type' => 'user_profile_item',
           '#title' => t('Items currently checked out:'),
-          '#value' => $msg,
+          '#value' => $checked_out_msg,
           '#attributes' => array('class' => 'millennium items'),
         );
-        
-        #number_holds_ready
 
-      } else {
-        drupal_set_message(t('Could not fetch checkout information from the library system. Please try again later.'), "error");
       }
+      else {
+        drupal_set_message(t('Could not fetch information from the library system. Please try again later.'), "error");
+      }
       break;
       
     case "delete":
@@ -156,7 +189,7 @@
       if ($millennium_auth_newuser) {
         // Should username map directly onto an email account (e.g.: jano -> jano@example.com)        
         #TODO: put these in settings... to recognize usernames-as-emails, setting for default domain
-        _millennium_auth_debug("millennium_auth_user($op): new user!");
+        #_millennium_auth_debug("millennium_auth_user($op): new user!");
         $regexp = '/^a0\d\d\d\d\d\d\d$/i';
         $domain = 'itesm.mx';
         
@@ -168,15 +201,15 @@
         // Store PIN data
         db_query("INSERT INTO {millennium_auth_users} (uid, lastname, name, pin) VALUES (%d, '%s', '%s', '%s')", 
           $account->uid,
-          $millennium_auth_newuser['lastname'], 
+          _millennium_auth_encrypt($millennium_auth_newuser['lastname']),
           $millennium_auth_newuser['username'], 
-          $millennium_auth_newuser['password']
+          _millennium_auth_encrypt($millennium_auth_newuser['password'])
         );
         _millennium_auth_debug("millennium_auth_user($op....) : Saved new user's PIN and last name");
         // Log new user
         watchdog(
           'millennium auth', 
-          'New user from millennium auth: %name', 
+          'New user from millennium_auth: %name', 
           array('%name' => $account->name) 
         );
         $account->roles = array();
@@ -207,9 +240,10 @@
   if (!is_array($items)) {
     return "";
   }
-  foreach($items as $item) {
+  $list = array();
+  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"], variable_get('millennium_auth_default_base_url', '')));
+    $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");
     }
@@ -236,7 +270,7 @@
                 
     $list[] = $item_html;
   }
-  return theme('item_list', $list, "");
+  return theme('item_list', $list);
 }
 
 
@@ -248,15 +282,22 @@
   #dsm($form_values);
   
   #$username, $password, $server
-  $username = $form_values["name"];
-  $password = $form_values["pass"];
-  $lastname = $form_values["lastname"];
+  if (variable_get('millennium_auth_login_fields', '2') == '3') {
+    $name = $form_values["lastname"];
+    $password = $form_values["pass"];
+    $code = $form_values["name"];
+  }
+  else {
+    $name = $form_values["name"];
+    $code = $form_values["pass"];
+  }
   
-  $lastname = $_POST["lastname"];
-  _millennium_auth_debug("millennium_auth_authenticate('$username', '$password', '$server'). Lastname = '$lastname'");
+  #$code = $_POST["lastname"];
+  #drupal_set_message("POST lastname = $code"); // DEBUG
+  _millennium_auth_debug("millennium_auth_authenticate('$name', '$password', '$server'). Lastname = '$code'");
     
   // Check user data against Millennium
-  $patroninfo_data = _millennium_auth_get_patroninfo_by_pindata($username, $lastname, $password);
+  $patroninfo_data = _millennium_auth_get_patroninfo_by_pindata($name, $code, $password);
   $result = false;
   if ($patroninfo_data["success"] === true) {
     _millennium_auth_debug("millennium_auth_authenticate(): _millennium_auth_get_patroninfo_by_pindata()[success] returned true");
@@ -264,30 +305,30 @@
     foreach($patroninfo_data as $index => $value) {
       _millennium_auth_debug("millennium_auth_auth(): $index = " . $value);
     }
-    $millennium_patroninfo[$username] = $patroninfo_data;
+    $millennium_patroninfo[$name] = $patroninfo_data;
     
     // Flag a successful attempt at authentication, for hook_user::insert ... new users will still not have their emails
     global $millennium_auth_newuser;
     $millennium_auth_newuser = array(
-      "lastname" => $lastname, 
-      "username" => $username, 
+      "lastname" => $code, 
+      "username" => $name, 
       "password" => $password
     );
     $result = true;
     
     // Log user in, or register new user if not already present.
-    user_external_login_register($username, 'millennium_auth');
+    user_external_login_register($name, 'millennium_auth');
 
     // Write session, update timestamp, run user 'login' hook.
     user_authenticate_finalize($form_values);
     
   } else {
-    
+    _millennium_auth_debug("Failed: millennium_auth_authenticate('$name', '$password', '$server')");
     $result = false;
     watchdog(
       'millennium auth', 
       'Error authenticating millennium user @lastname, @username: @errormsg', 
-      array('@username' => $username, '@errormsg' => $patroninfo_data['errormsg']) 
+      array('@username' => $name, '@errormsg' => $patroninfo_data['errormsg']) 
     );
   }
   patroninfo_end_session($patroninfo_data["session_id"]);
@@ -301,6 +342,8 @@
   #_millennium_auth_debug("_millennium_auth_get_pincache($uid)");
   $data = db_fetch_array( db_query("SELECT * FROM {millennium_auth_users} WHERE uid = %d", $uid) );
   if ($data) {
+    $data['pin'] = _millennium_auth_decrypt($data['pin']);
+    $data['lastname'] = _millennium_auth_decrypt($data['lastname']);
     return $data;
   } else {
     return false;
@@ -331,10 +374,10 @@
 /**
  * Fetch patroninfo using name, lastname and pin
  */
-function _millennium_auth_get_patroninfo_by_pindata($username, $lastname, $pin) {
+function _millennium_auth_get_patroninfo_by_pindata($name, $code, $pin) {
   static $patroninfo_data_cache;
   
-  $key = "$username|$lastname|" . crypt($pin, "salt");
+  $key = crypt("$name:$code:$pin", "salt");
   #drupal_set_message("Trying to get info for $key");
   
   if ($patroninfo_data_cache[$key]) {
@@ -345,7 +388,7 @@
   // Check database cache before connecting
   $cid = "millennium_auth_$key";
   $cache = cache_get($cid);
-  if (! empty($cache->data)) {
+  if (! empty($cache->data) && $cache->expire > time()) {
     #drupal_set_message("Fetched $key from DB cache");
     $patroninfo_data = $cache->data;
     $patroninfo_data_cache[$key] = $patroninfo_data;
@@ -359,7 +402,8 @@
   }
 
   // Connect to Millennium and get the patron's information
-  $patroninfo_data = patroninfo_start_session($base_url, $username, $lastname, $pin);
+  $date_format = variable_get('millennium_auth_date_format', 'MDY');
+  $patroninfo_data = patroninfo_start_session($base_url, $name, $code, $pin, $date_format);
   
   if ($patroninfo_data["success"] != true) {
   	_millennium_auth_debug("_millennium_auth_get_patroninfo_by_pindata(): Error: ". $patroninfo_data["errormsg"]);
@@ -404,36 +448,39 @@
   }
   
   // Login form
-  if ($form_id == 'user_login_block' || $form_id == 'user_login') { 
-    #drupal_set_message(var_export($form,TRUE));
-    $form['lastname'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Your last name'),
-      '#size' => 15,
-      '#description' => t('Example: smith'),
-      '#default_value' => '',
-      '#weight' => -50,
-      '#attributes' => array('tabindex' => 1),
-      #'#required' => 1,
-    );
-    $form['name'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Your Library ID'),
-      '#size' => 10,
-      '#description' => check_plain(variable_get('millennium_auth_id_message', t(MILLENNIUM_AUTH_DEFAULT_ID_MSG))),
-      '#default_value' => '',
-      '#attributes' => array('tabindex' => 2),
-      '#required' => 1,
-      #'#weight' => -50,
-    );
+  
+  if ($form_id == 'user_login_block' || $form_id == 'user_login') {
+    if (variable_get('millennium_auth_login_fields', '2') == '3') {
+      $form['lastname'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Your last name'),
+        '#size' => 15,
+        '#description' => t('Example: smith'),
+        '#default_value' => '',
+        '#weight' => -50,
+        '#attributes' => array('tabindex' => 1),
+        #'#required' => 1,
+      );
+      // Add tabindex to elements.
+      $form['name']['#attributes']['tabindex'] = 2;
+      $form['pass']['#attributes']['tabindex'] = 3;
+      $form['submit']['#attributes']['tabindex'] = 4;
+    }
+    else {
+      $form['lastname'] = array(
+        '#type' => 'hidden',
+        '#value' => '',
+      );
+    }
     
+    // Alter username element.
+    $form['name']['#title'] = t('Your Library ID');
+    $form['name']['#description'] = check_plain(variable_get('millennium_auth_id_message', t(MILLENNIUM_AUTH_DEFAULT_ID_MSG)));
+    
+    // Alter password element.
     $form['pass']['#title'] = t('Library PIN');
     $form['pass']['#size'] = 12;
     $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
-    $form['submit']['#attributes'] = array('tabindex' => 4);
         
     // Add our own validation and submit function to the node_form
     $final_validator = array_pop($form['#validate']);
@@ -453,7 +500,6 @@
  * Validation function for user login form.
  */
 function millennium_auth_validate($form, &$form_state) {
-  global $millennium_auth_patronlastname;
   global $user;
   
   if (!empty($user->uid)) {
@@ -469,8 +515,8 @@
   // Check if user is not in database... then the lastname is required
   $uid_mil = db_result(db_query("SELECT uid FROM {millennium_auth_users} WHERE name='%s'", $form_state['values']["name"] ) );
   $uid_dru = db_result(db_query("SELECT uid FROM {users} WHERE name='%s'", $form_state['values']["name"] ) );
-  if (!$uid && !$uid_dru && trim($form_state['values']["lastname"])=="" ) {
-    form_set_error('name', t('The last name is required for all first-time users') );
+  if (!$uid && !$uid_dru && variable_get('millennium_auth_login_fields', '2') == '3' && trim($form_state['values']["lastname"])=="" ) {
+    form_set_error('lastname', t('The last name is required for all first-time users') );
   }
   
   if (!millennium_auth_authenticate($form_state['values'])) {
@@ -488,3 +534,29 @@
   #drupal_set_message($msg);
   #}  
 }
+
+function _millennium_auth_encrypt($string) {
+  $key = MILLENNIUM_AUTH_KEY;
+  $result = '';
+  for ($i=1; $i<=strlen($string); $i++)
+  {
+    $char = substr($string, $i-1, 1);
+    $keychar = substr($key, ($i % strlen($key))-1, 1);
+    $char = chr(ord($char)+ord($keychar));
+    $result .= $char;
+  }
+  return base64_encode($result);
+}
+
+function _millennium_auth_decrypt($string) {
+  $string = base64_decode($string);
+  $key = MILLENNIUM_AUTH_KEY;
+  $result = '';
+  for ($i=1; $i<=strlen($string); $i++) {
+    $char = substr($string, $i-1, 1);
+    $keychar = substr($key, ($i % strlen($key))-1, 1);
+    $char = chr(ord($char)-ord($keychar));
+    $result .= $char;
+  }
+  return $result;
+}
Index: patroninfo.inc
--- patroninfo.inc Base (1.1.2.6)
+++ patroninfo.inc Locally Modified (Based On 1.1.2.6)
@@ -1,20 +1,22 @@
 <?php
 // $Id: patroninfo.inc,v 1.1.2.6 2010/05/30 02:58:54 janusman Exp $
 
-## patroninfo.inc
 /**
- * TODO:
- *  Check regular expressions work on other systems.
+ * @file patroninfo.inc
+ *
+ * Functions to authenticate against III Millennium OPACs and return information
+ * like current checkouts, number of holds, total fines and more.
+ *
+ * TODOs:
+ *  Check that regular expressions work on other systems.
  *  Renew functions not working.
  *  Add support for library messages: /patroninfo/XXXXX/msg
  *  Add support for showing full fines screen: /patroninfo/XXXXX/overdues
  *  Eliminate dependency on CURL library.
  */
  
-
 /**
  * Regular expressions for screen-scraping
- * 
  */
 
 // Error messages in patroninfo screen
@@ -25,13 +27,9 @@
 // <strong>Garza Gonzalez, Alejandro</strong><br>
 define("PATRONINFO_NAMES_RE", "/<strong>([A-Z].*?)(, [A-Z].*|)<\/strong><br \/>/i");
 
-// Checkouts
-// <a href="/patroninfo*eng/120796/items" onClick="return replace_or_redraw( '/patroninfo*eng/120796/items' )">16 Ejemplares actualmente prestado(s)</a>
-# define("PATRONINFO_CHECKOUTS_RE", "/([0-9]+) ITEMS CHECKED OUT/i");
-
 // Holds
 // <a href="/patroninfo/304550/holds" onClick="return replace_or_redraw( '/patroninfo/304550/holds' )">2 requests (holds). 2 ready for pickup.</a>
-define("PATRONINFO_GLOBAL_HOLDS_RE", "/<a href=.(.patroninfo.*?\\/[0-9]+.holds). [^>]*>([0-9]+) requests \(holds\)\. *(?: ([0-9]+) ready for pickup|)/");
+define("PATRONINFO_GLOBAL_HOLDS_RE", "/<a href=.(.patroninfo.*?\\/[0-9]+.holds). [^>]*>([0-9]+) requests* \(holds*\)\. *(?:([0-9]+) ready for pickup|)/");
 
 // Fines
 // 
@@ -44,11 +42,8 @@
 
 // Number of renewals
 // <td align="left" class="patFuncStatus"> DUE 02-03-09 +2 HOLDS <span  class="patFuncRenewCount">Renewed 13 times</span>
-define("PATRONINFO_ITEM_HOLDS_RE", "/.*\+([0-9]+) HOLDS.*/i");
+define("PATRONINFO_ITEM_HOLDS_RE", "/.*\+([0-9]+) HOLDS*.*/i");
 
-// Fines per item
-// CARGO/MULTA(hasta ahora) $10.00
-define("PATRONINFO_ITEM_FINES_RE", "/\\$([0-9\.]+)/");
 
 /**
  * Wrapper for preg_match().
@@ -125,7 +120,7 @@
 		);
 	}
 
-	if (preg_match('/patActionsLinks/', $response)) {
+	if (preg_match('/patActionsLinks|patStatusLinks|patnavRow/', $response)) {
 		return array(
 			"success" => true,
 			"response" => $response
@@ -142,7 +137,7 @@
 /**
  * Begins an authenticated user session with Millennium through http.
  */
-function patroninfo_start_session($millennium_baseurl, $id, $lastname, $pin) {
+function patroninfo_start_session($millennium_baseurl, $name, $code, $pin, $date_format = 'MDY') {
 global $PI_post_data;
 
 	// Generate a new sessionid (random number)
@@ -150,16 +145,13 @@
 	register_shutdown_function('patroninfo_end_session', $session_id);
 	
 	// Store name/code/pin for future callse
-	$post_data = "name={$lastname}&code={$id}&pin={$pin}";
+	$post_data = "name={$name}&code={$code}&pin={$pin}";
 	$PI_post_data[$session_id] = $post_data;
-	
-	// TODO: Make this configurable?
   $login_page = $millennium_baseurl ."/patroninfo*eng";
 
+  // Conect to login page.
 	$r = patroninfo_request($session_id, $login_page);
-	
 	if ($r["success"] === false) {
-		#print "Error es: ".$error;
 		return array(
 		  "success" => false, 
 		  "errormsg" => t("Connection error: @errormsg", array("@errormsg" => $r["errormsg"]))
@@ -167,7 +159,7 @@
 	}
 	$fetched_html = $r["response"];
 	
-	// Check for any error messages
+	// Check for any error messages inside page HTML.
 	$matches = patroninfo_scrape(PATRONINFO_ERRORMSG_RE, $fetched_html);
 	if (sizeof($matches)>0) {
 		return array(
@@ -177,9 +169,8 @@
 	}
 	
 	$patroninfo_data["session_id"] = $session_id;
-	#$patroninfo_data["postdata"] = $post_data;
 
-	$ok = preg_match("/<a href=\"\/patroninfo.*?\*[a-z]+\/([0-9]+)\/top\/\">/", $fetched_html, $matches);
+	$ok = preg_match("/<a href=\"\/patroninfo.*?\/([0-9]+)\/(top|holds|items|overdues)\/\">/", $fetched_html, $matches);
 	if (!$ok) {
 		return array(
 		  "success" => false, 
@@ -188,23 +179,29 @@
 	}
 	$id = $matches[1];
 	$url_checkouts = "$login_page/$id/items";
-	$url_holds = "$login_page/$id/holds";
+	$url_holds = "$login_page/$id/sortedholds";
 	$url_fines = "$login_page/$id/overdues";
 	
-	// Scrape items screen
-	$r = patroninfo_request($session_id, $url_checkouts);
-  $fetched_html = $r["response"];
-  // First, last names
-	$matches = patroninfo_scrape(PATRONINFO_NAMES_RE, $fetched_html);
-	$patroninfo_data["lastname"] = $matches[1];
-	$patroninfo_data["firstname"] = str_replace(", ", "", $matches[2]);
-	
 	// Grab the checked out item list; the URL for that screen is in $url_checkouts
-	$r = patroninfo_request($session_id, $url_checkouts);
+  $r = patroninfo_request($session_id, $url_checkouts);
+  if ($r["success"] === false) {
+		return array(
+		  "success" => false,
+		  "errormsg" => t("Connection error: @errormsg", array("@errormsg" => $r["errormsg"]))
+		);
+	}
   $fetched_html = $r["response"];
-  $items = patroninfo_scrape_items($fetched_html);
+  // Scrape checked-out items screen.
+  $items = patroninfo_scrape_items($fetched_html, $date_format);
   $patroninfo_data["checkouts"] = $items;
+  // Scrape first, last names from current screen.
+	$matches = patroninfo_scrape(PATRONINFO_NAMES_RE, $fetched_html);
+  if (sizeof($matches)>0) {
+    $patroninfo_data["lastname"] = $matches[1];
+    $patroninfo_data["firstname"] = str_replace(", ", "", $matches[2]);
+  }
 
+
 	// Get global holds
 	$r = patroninfo_request($session_id, $url_holds);
 	$fetched_html = $r["response"];
@@ -233,7 +230,7 @@
 /**
  * Returns an array of item; each item is an array containing the item_recnum, title, barcode, status, message, fine, callno and duedate_timestamp
  */
-function patroninfo_scrape_items($fetched_html) {
+function patroninfo_scrape_items($fetched_html, $date_format) {
 	
 	/* SAMPLE items screen:    
   <table border="0" class="patFunc">
@@ -357,68 +354,74 @@
 
 	*/
 	
-	/* Fines are optionally shown like this:
-	  <td align="left" class="patFuncStatus"> Prestado hasta 03-05-06 <br><font color="red">CARGO/MULTA(hasta ahora) $10.00</font>
-	*/
-	#echo "<pre>$fetched_html</pre>\n\n\n\n\n";
+  $items = array();
+
+  /** 
+   * Sample HTML for one item:
+   *
+   * <tr class="patFuncEntry"><td align="left" class="patFuncMark"><input type="checkbox" name="renew4" value="i3163218" /></td><td align="left" class="patFuncTitle"><a href="/patroninfo/120796/item&1177514"> Estudio exploratorio acerca del nivel de satisfacc </a>
+        <br />
+
+        </td>
+        <td align="left" class="patFuncBarcode"> 30002007139439 </td>
+        <td align="left" class="patFuncStatus"> DUE 30-03-09 <br /><font color="red">FINE(up to now) $5.00</font> <span  class="patFuncRenewCount">Renewed 6 times</span>
+
+        </td>
+        <td align="left" class="patFuncCallNo"> LC5803.C65 G84 2008  </td>
+        </tr>
+   *
+   * Fines are optionally shown like this:
+   * 
+   *    <td align="left" class="patFuncStatus"> Prestado hasta 03-05-06 <br><font color="red">CARGO/MULTA(hasta ahora) $10.00</font>
+   */
+  $re = "/<tr class=.patFuncEntry.>";
+  $re .= ".*? value=\"(?:cancel|)(i[0-9]{5,10}x*).*?\""; //1
+  $re .= ".*?patFuncTitle[^>]+>.*?>([^<]+)<"; //2
+  $re .= "(?:.*?patFuncBarcode[^>]+>([^<]+)<|)"; //3
+  $re .= ".*?patFuncStatus[^>]+>([^<]+)(?:.*?\\\$([0-9.]+)|)"; //4, 5
+  $re .= "(?:.*?patFuncRenewCount[^>]+>.*?Renewed ([0-9]+) time.*?|)"; //6
+  $re .= ".*?patFuncCallNo[^>]+>([^<]+)."; //7
+  $re .= "*?<.tr>/s";
+
+	$result = preg_match_all($re, $fetched_html, $matches, PREG_SET_ORDER);
+  if ($matches) {
+    #dpm($matches);
+    foreach($matches as $match) {
+      $item = array();
+      /*
+        Array
+        (
+            [0] => [... lots of html...]
+            [1] => i3163218
+            [2] =>  Estudio exploratorio acerca del nivel de satisfacc
+            [3] =>  30002007139439
+            [4] =>  DUE 30-03-09
+            [5] => 5.00
+            [6] => 6
+            [7] =>  LC5803.C65 G84 2008
+        )
+      */
+      #print_r($match);
+      $item["item_recnum"] = $match[1];
+      $item["title"] = trim(utf8_encode($match[2]));
+      $item["barcode"] = trim($match[3]);
+      $item["status"] = trim(utf8_encode($match[4]));
+      $item["fine"] = (float)$match[5];
+      $item["renew_count"] = trim(utf8_encode($match[6]));
+      $item["callno"] = trim($match[7]);
+      $item["duedate_timestamp"] = patroninfo_itemstatus_due_to_timestamp($item["status"], $date_format);
+
+      // Get holds per item
+      // DUE 02-03-09 +2 HOLDS
+      if (preg_match(PATRONINFO_ITEM_HOLDS_RE, $item["status"], $matches)) {
+        $item["hold_count"] = intval($matches[1]);
+      }
+      $items[] = $item;
+    }
+
+    $items = patroninfo_items_duedate_sort($items);
+  }
 	
-	$result = preg_match_all("/<tr class=.patFuncEntry.>.*? value=\"(i[0-9]+)\".*?patFuncTitle[^>]+>.*?>([^<]+)<.*?patFuncBarcode\">([^<]+).*?patFuncStatus\">([^<]+)(?:.*?\\\$([0-9.]+)|)(?:.*?patFuncRenewCount.*?>.*?Renewed ([0-9]+) time.*?|).*?patFuncCallNo\">([^<]+).*?<.tr>/s", $fetched_html, $matches, PREG_SET_ORDER);
-	#print_r($matches);
-	foreach($matches as $match) {
-  	$item = array();
-		/*
-      Array
-      (
-          [0] => <tr class="patFuncEntry"><td align="left" class="patFuncMark"><input type="checkbox" name="renew4" value="i3163218" /></td><td align="left" class="patFuncTitle"><a href="/patroninfo/120796/item&1177514"> Estudio exploratorio acerca del nivel de satisfacc </a>
-      <br />
-      
-      </td>
-      <td align="left" class="patFuncBarcode"> 30002007139439 </td>
-      <td align="left" class="patFuncStatus"> DUE 30-03-09 <br /><font color="red">FINE(up to now) $5.00</font> <span  class="patFuncRenewCount">Renewed 6 times</span>
-      
-      </td>
-      <td align="left" class="patFuncCallNo"> LC5803.C65 G84 2008  </td>
-      </tr>
-          [1] => i3163218
-          [2] =>  Estudio exploratorio acerca del nivel de satisfacc 
-          [3] =>  30002007139439 
-          [4] =>  DUE 30-03-09 
-          [5] => 5.00
-          [6] => 6
-          [7] =>  LC5803.C65 G84 2008  
-      )
-		*/
-		print_r($match);
-		$item["item_recnum"] = $match[1];
-		$item["title"] = trim(utf8_encode($match[2]));
-		$item["barcode"] = trim($match[3]);
-		$item["status"] = trim(utf8_encode($match[4]));
-		$item["fine"] = (float)$match[5];
-		$item["renew_count"] = trim(utf8_encode($match[6]));
-		$item["callno"] = trim($match[7]);
-		$item["duedate_timestamp"] = patroninfo_itemstatus_due_to_timestamp($item["status"]);
-		
-		// Get holds per item
-		// DUE 02-03-09 +2 HOLDS
-		if (preg_match(PATRONINFO_ITEM_HOLDS_RE, $item["status"], $matches)) {
-  		$item["hold_count"] = intval($matches[1]);
-		}
-		
-		// Get fine per item
-		// CARGO/MULTA(hasta ahora) $10.00
-		// TODO ENGLISH?
-		/*
-		if (preg_match(PATRONINFO_ITEM_FINES_RE, $item["status"], $matches)) {
-			$item["fine"] = (float)$matches[1];
-		} else {
-  		$item["fine"] = 0;
-		}
-		*/
-		$items[] = $item;
-	}
-	
-	$items = patroninfo_items_duedate_sort($items);
-	
 	return $items;
 }
 
@@ -490,15 +493,20 @@
 /**
  * Helper function that converts the III due date to a unix timestamp
  */
-function patroninfo_itemstatus_due_to_timestamp($itemstatus) {
+function patroninfo_itemstatus_due_to_timestamp($itemstatus, $date_format = 'MDY') {
 	$m = patroninfo_scrape(PATRONINFO_ITEM_DUEDATE_RE, $itemstatus);
 	if ($m == false) {
   	return false;
 	}
-
+  
 	// Make timestamp
 	// int mktime  ([ int $hour  [, int $minute  [, int $second  [, int $month  [, int $day  [, int $year  [, int $is_dst  ]]]]]]] )
-	$ts = mktime(0, 0 ,0, $m[2], $m[1], "20".$m[3]);
+  if ($date_format == 'MDY') {
+    $ts = mktime(0, 0 ,0, $m[1], $m[2], "20".$m[3]);
+  }
+  else {
+  	$ts = mktime(0, 0 ,0, $m[2], $m[1], "20".$m[3]);
+  }
 	return $ts;
 }
 
@@ -506,9 +514,15 @@
  * Helper function to sort the items array returned by patroninfo_scrape_items() by duedate timestamp
  */
 function patroninfo_items_duedate_sort($items) {
-	function _cmp($a, $b) {
-		return ($a[duedate_timestamp]<$b[duedate_timestamp]) ? -1 : 1;
-	}
-	usort($items, "_cmp");
+  static $firstrun = TRUE;
+  if ($firstrun) {
+    function _cmp($a, $b) {
+      return ($a[duedate_timestamp]<$b[duedate_timestamp]) ? -1 : 1;
+    }
+    $firstrun = FALSE;
+  }
+  if (is_array($items)) {
+    usort($items, "_cmp");
+  }
 	return $items;
 }
