diff --git a/config/vufind/XCNCIP2.ini b/config/vufind/XCNCIP2.ini
index 20e177adab7ae078f76a4d499c4a53a8ede48a31..76be7e34c7e0a19adf82f32dbb85d604325efe35 100644
--- a/config/vufind/XCNCIP2.ini
+++ b/config/vufind/XCNCIP2.ini
@@ -1,5 +1,34 @@
 [Catalog]
 ; Base URL for the XC NCIP Toolkit's NCIP responder:
 url         = http://myuniversity.edu:8080/ncipv2/NCIPResponder
+
 ; Your library's Agency ID (ILSDefaultAgency setting in driver_config.properties):
-agency      = "My University"
\ No newline at end of file
+agency      = "My University"
+
+; Pickup location definitions: CSV file 
+;
+;     Format: [agency],[locationID],[locationDisplay]
+;
+;     e.g., 
+;         (for consortium=false)
+;         My University,1,Main Circulation Desk
+;         My University,2,Stacks
+;
+;     e.g., 
+;         (for consortium=true)
+;         Agency1,1,Agency1 - Main Circulation Desk
+;         Agency1,2,Agency1 - Stacks
+;         Agency2,11,Agency2 - Main Circulation Desk
+;         Agency2,12,Agency2 - Stacks
+pickupLocationsFile = "XCNCIP2_locations.txt"
+
+;-----------------------------------------------------------------
+; Consortium settings below:
+;-----------------------------------------------------------------
+
+; Is this a consortium?
+consortium  = false
+
+; If consortium is true, list all valid agencies
+;agency[]  = "Agency1"
+;agency[]  = "Agency2"
diff --git a/config/vufind/XCNCIP2_locations.txt b/config/vufind/XCNCIP2_locations.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7c1b6805ea5d993f8c05cc4407afb30a7a2656fd
--- /dev/null
+++ b/config/vufind/XCNCIP2_locations.txt
@@ -0,0 +1,2 @@
+My University,1,Main Circulation Desk
+My University,2,Stacks
diff --git a/module/VuFind/src/VuFind/ILS/Driver/XCNCIP2.php b/module/VuFind/src/VuFind/ILS/Driver/XCNCIP2.php
index 01c9864dee6c0e88983683531900ad2908a0d279..a3e4278ecd16f8d0400a7248eeaca470fc6c1340 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/XCNCIP2.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/XCNCIP2.php
@@ -26,7 +26,8 @@
  * @link     http://vufind.org/wiki/vufind2:building_an_ils_driver Wiki
  */
 namespace VuFind\ILS\Driver;
-use VuFind\Exception\ILS as ILSException;
+use VuFind\Exception\ILS as ILSException,
+    VuFind\Config\Locator as ConfigLocator;
 
 /**
  * XC NCIP Toolkit (v2) ILS Driver
@@ -46,6 +47,34 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
      */
     protected $httpService = null;
 
+    /**
+     * Is this a consortium? Default: false
+     *
+     * @var boolean
+     */
+    protected $consortium = false;
+
+    /**
+     * Agency definitions (consortial) - Array list of consortium members
+     *
+     * @var array
+     */
+    protected $agency = array();
+
+    /**
+     * NCIP server URL
+     *
+     * @var string
+     */
+    protected $url;
+
+    /**
+     * Pickup locations
+     *
+     * @var array
+     */
+    protected $pickupLocations = array();
+    
     /**
      * Set the HTTP service to be used for HTTP requests.
      *
@@ -72,6 +101,50 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         if (empty($this->config)) {
             throw new ILSException('Configuration needs to be set.');
         }
+        
+        $this->url = $this->config['Catalog']['url'];
+        if ($this->config['Catalog']['consortium']) {
+            $this->consortium = true;
+            foreach ($this->config['Catalog']['agency'] as $agency) {
+                $this->agency[$agency] = 1;
+            }
+        } else {
+            $this->consortium = false;
+            if (is_array($this->config['Catalog']['agency'])) {
+               $this->agency[$this->config['Catalog']['agency'][0]] = 1;
+            } else {
+               $this->agency[$this->config['Catalog']['agency']] = 1;
+            }
+        }
+
+        $this->loadPickupLocations($this->config['Catalog']['pickupLocationsFile']);
+    }
+
+    /**
+     * Load pickup locations
+     *
+     * Loads pickup location information from configuration file.
+     *
+     * @throws ILSException
+     * @return void
+     */
+    public function loadPickupLocations($filename)
+    {
+        // Load pickup locations file:
+        $pickupLocationsFile = ConfigLocator::getConfigPath($filename, 'config/vufind');
+        if (!file_exists($pickupLocationsFile)) {
+            throw new ILSException("Cannot load pickup locations file: {$pickupLocationsFile}.");
+        }
+        if (($handle = fopen($pickupLocationsFile, "r")) !== FALSE) {
+            while (($data = fgetcsv($handle)) !== FALSE) {
+                $this->pickupLocations[$data[0]][] = 
+                    array(
+                        'locationID' => $data[1],
+                        'locationDisplay' => $data[2]
+                    );
+            }
+            fclose($handle);
+        }
     }
 
     /**
@@ -86,7 +159,11 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         // Make the NCIP request:
         try {
             $client = $this->httpService
-                ->createClient($this->config['Catalog']['url']);
+                ->createClient($this->url);
+            // Set timeout value
+            $timeout = isset($this->config['Catalog']['http_timeout'])
+            ? $this->config['Catalog']['http_timeout'] : 30;
+            $client->setOptions(array('timeout' => $timeout));
             $client->setRawBody($xml);
             $client->setEncType('application/xml; "charset=utf-8"');
             $result = $client->setMethod('POST')->send();
@@ -109,68 +186,136 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         }
     }
 
+
+    /**
+     * Given a chunk of the availability response, extract the values needed
+     * by VuFind.
+     *
+     * @param $current Current LUIS holding chunk.
+     *
+     * @return array of status information for this holding
+     */
+    protected function getStatusForChunk($current)
+    {
+        $status = $current->xpath(
+                'ns1:ItemOptionalFields/ns1:CirculationStatus'
+        );
+        $status = empty($status) ? '' : (string)$status[0];
+        
+        $itemId = $current->xpath(
+
+                'ns1:ItemId/ns1:ItemIdentifierValue'
+        );
+        $item_id = (string)$itemId[0];
+        
+        $itemCallNo = $current->xpath(
+         
+                'ns1:ItemOptionalFields/ns1:ItemDescription/ns1:CallNumber'
+        );
+        $itemCallNo = (string)$itemCallNo[0];
+        
+        $location = $current->xpath(    
+                'ns1:ItemOptionalFields/ns1:Location/ns1:LocationName/' .
+                'ns1:LocationNameInstance/ns1:LocationNameValue'
+        );
+        $location = (string)$location[0];
+        
+        return array(
+                //'id' => ...
+                'status' => $status,
+                'location' => $location,
+                'callnumber' => $itemCallNo,
+                'availability' => ($status == "Not Charged"),
+                'reserve' => 'N',       // not supported
+        );  
+    }
+
     /**
      * Given a chunk of the availability response, extract the values needed
      * by VuFind.
      *
      * @param array $current Current XCItemAvailability chunk.
+     * @param string $aggregate_id (Aggregate) ID of the consortial record
+     * @param string $bib_id Bib ID of one of the consortial record's source record(s)
      *
      * @return array
      */
-    protected function getHoldingsForChunk($current)
+    protected function getHoldingsForChunk($current, $aggregate_id = null, $bib_id = null)
     {
         // Maintain an internal static count of line numbers:
         static $number = 1;
 
+        $current->registerXPathNamespace('ns1', 'http://www.niso.org/2008/ncip');
+        
         // Extract details from the XML:
         $status = $current->xpath(
-            'ns1:HoldingsSet/ns1:ItemInformation/' .
             'ns1:ItemOptionalFields/ns1:CirculationStatus'
         );
         $status = empty($status) ? '' : (string)$status[0];
 
-        $id = $current->xpath(
-            'ns1:BibliographicId/ns1:BibliographicItemId/' .
-            'ns1:BibliographicItemIdentifier'
-        );
+        $itemId = $current->xpath('ns1:ItemId/ns1:ItemIdentifierValue');
+
+        $itemAgencyId = $current->xpath('ns1:ItemId/ns1:AgencyId');
 
         // Pick out the permanent location (TODO: better smarts for dealing with
         // temporary locations and multi-level location names):
-        $locationNodes = $current->xpath('ns1:HoldingsSet/ns1:Location');
-        $location = '';
-        foreach ($locationNodes as $curLoc) {
-            $type = $curLoc->xpath('ns1:LocationType');
-            if ((string)$type[0] == 'Permanent') {
-                $tmp = $curLoc->xpath(
-                    'ns1:LocationName/ns1:LocationNameInstance/ns1:LocationNameValue'
-                );
-                $location = (string)$tmp[0];
-            }
-        }
+//         $locationNodes = $current->xpath('ns1:HoldingsSet/ns1:Location');
+//         $location = '';
+//         foreach ($locationNodes as $curLoc) {
+//             $type = $curLoc->xpath('ns1:LocationType');
+//             if ((string)$type[0] == 'Permanent') {
+//                 $tmp = $curLoc->xpath(
+//                     'ns1:LocationName/ns1:LocationNameInstance/ns1:LocationNameValue'
+//                 );
+//                 $location = (string)$tmp[0];
+//             }
+//         }
+
+        $tmp = $current->xpath('ns1:ItemOptionalFields/ns1:Location/' .
+            'ns1:LocationName/ns1:LocationNameInstance/ns1:LocationNameValue'
+        );
+        $location = (string)$tmp[0];
 
-        // Get both holdings and item level call numbers; we'll pick the most
-        // specific available value below.
-        $holdCallNo = $current->xpath('ns1:HoldingsSet/ns1:CallNumber');
-        $holdCallNo = (string)$holdCallNo[0];
         $itemCallNo = $current->xpath(
-            'ns1:HoldingsSet/ns1:ItemInformation/' .
             'ns1:ItemOptionalFields/ns1:ItemDescription/ns1:CallNumber'
         );
         $itemCallNo = (string)$itemCallNo[0];
+        
+        $volume = $current->xpath(
+            'ns1:ItemOptionalFields/ns1:ItemDescription/' . 
+            'ns1:HoldingsInformation/ns1:UnstructuredHoldingsData'
+        );
+        $volume = (string)$volume[0];
+
+        if ($status === "Not Charged") {
+            $holdType = "hold";
+        } else {
+            $holdType = "recall";
+        }
 
         // Build return array:
         return array(
-            'id' => empty($id) ? '' : (string)$id[0],
+            'id' => empty($aggregate_id) ? (empty($bib_id) ? '' : $bib_id) : $aggregate_id,
             'availability' => ($status == 'Not Charged'),
             'status' => $status,
+            'item_id' => (string)$itemId[0],
+            'bib_id' => $bib_id,
+            'item_agency_id' => (string)$itemAgencyId[0],
+            'aggregate_id' => $aggregate_id,
             'location' => $location,
             'reserve' => 'N',       // not supported
-            'callnumber' => empty($itemCallNo) ? $holdCallNo : $itemCallNo,
+            'callnumber' => $itemCallNo,
             'duedate' => '',        // not supported
-            'number' => $number++,
+            //'number' => $number++,
+            'number' => $volume,
             // XC NCIP does not support barcode, but we need a placeholder here
             // to display anything on the record screen:
-            'barcode' => 'placeholder' . $number
+            'barcode' => 'placeholder' . $number,
+            'is_holdable'  => true,
+            'addLink' => true,
+            'holdtype' => $holdType,
+            'storageRetrievalRequest' => 'auto',
+            'addStorageRetrievalRequestLink' => 'true',
         );
     }
 
@@ -201,8 +346,17 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
      *
      * @return string            XML request
      */
-    protected function getStatusRequest($idList, $resumption = null)
+    protected function getStatusRequest($idList, $resumption = null, $agency = null)
     {
+        // cedelis:
+        //if (is_null($agency)) $agency = $this->agency[0];
+        
+        // pzurek:
+        //if (is_null($agency)) $agency = array_keys($this->agency)[0];
+        // The above does not work on older versions of php
+        $keys = array_keys($this->agency);
+        if (is_null($agency)) $agency = $keys[0];
+
         // Build a list of the types of information we want to retrieve:
         $desiredParts = array(
             'Bibliographic Description',
@@ -223,12 +377,14 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         // Add the ID list:
         foreach ($idList as $id) {
             $xml .= '<ns1:BibliographicId>' .
-                    '<ns1:BibliographicItemId>' .
-                        '<ns1:BibliographicItemIdentifier>' .
+                    '<ns1:BibliographicRecordId>' .
+                        '<ns1:BibliographicRecordIdentifier>' .
                             htmlspecialchars($id) .
-                        '</ns1:BibliographicItemIdentifier>' .
-                        '<ns1:AgencyId>LOCAL</ns1:AgencyId>' .
-                    '</ns1:BibliographicItemId>' .
+                        '</ns1:BibliographicRecordIdentifier>' .
+                        '<ns1:AgencyId>' .
+                            htmlspecialchars($agency) .
+                        '</ns1:AgencyId>' .
+                    '</ns1:BibliographicRecordId>' .
                 '</ns1:BibliographicId>';
         }
 
@@ -265,6 +421,11 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
     public function getStatuses($idList)
     {
         $status = array();
+
+        if ($this->consortium) {
+            return $status; // (empty) TODO: add support for consortial statuses. 
+        }
+
         $resumption = null;
         do {
             $request = $this->getStatusRequest($idList, $resumption);
@@ -275,17 +436,38 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
 
             // Build the array of statuses:
             foreach ($avail as $current) {
-                // Get data on the current chunk of data:
-                $chunk = $this->getHoldingsForChunk($current);
-
-                // Each bibliographic ID has its own key in the $status array; make
-                // sure we initialize new arrays when necessary and then add the
-                // current chunk to the right place:
-                $id = $chunk['id'];
-                if (!isset($status[$id])) {
-                    $status[$id] = array();
+                $bib_id = $current->xpath(
+                        'ns1:BibliographicId/ns1:BibliographicRecordId/' .
+                        'ns1:BibliographicRecordIdentifier'
+                );
+                $bib_id = (string)$bib_id[0];
+                
+                $holdings = $current->xpath('ns1:HoldingsSet');
+                
+                foreach ($holdings as $current) {
+
+                    $holdCallNo = $current->xpath('ns1:CallNumber');
+                    $holdCallNo = (string)$holdCallNo[0];
+                    
+                    $items = $current->xpath('ns1:ItemInformation');
+                    
+                    foreach ($items as $item) {
+                        // Get data on the current chunk of data:
+                        $chunk = $this->getStatusForChunk($item);
+        
+                        $chunk['callnumber'] = empty($chunk['callnumber']) ? 
+                            $holdCallNo : $chunk['callnumber'];
+                        
+                        // Each bibliographic ID has its own key in the $status array; make
+                        // sure we initialize new arrays when necessary and then add the
+                        // current chunk to the right place:
+                        $chunk['id'] = $bib_id;
+                        if (!isset($status[$id])) {
+                            $status[$id] = array();
+                        }
+                        $status[$bib_id][] = $chunk;
+                    }
                 }
-                $status[$id][] = $chunk;
             }
 
             // Check for resumption token:
@@ -296,6 +478,72 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         } while (!empty($resumption));
         return $status;
     }
+    
+    /**
+     * Get Consortial Holding
+     *
+     * This is responsible for retrieving the holding information of a certain
+     * consortial record.
+     *
+     * @param string $id     The record id to retrieve the holdings for
+     * @param array $ids     The (consortial) source records for the record id
+     * @param array  $patron Patron data
+     *
+     * @throws \VuFind\Exception\Date
+     * @throws ILSException
+     * @return array         On success, an associative array with the following
+     * keys: id, availability (boolean), status, location, reserve, callnumber,
+     * duedate, number, barcode.
+     */
+    public function getConsortialHoldings($id, array $patron = null, array $ids = null)
+    {
+        $aggregate_id = $id;
+        
+        $item_agency_id = array();
+        if (! is_null($ids)) {
+            foreach ($ids as $_id) { 
+                // Need to parse out the 035$a format, e.g., "(Agency) 123"
+                if (preg_match('/\(([^\)]+)\)\s*([0-9]+)/', $_id, $matches)) {
+                    $matched_agency = $matches[1];
+                    $matched_id = $matches[2];
+                    if ($this->agency[$matched_agency]) {
+                        $item_agency_id[$matched_agency] = $matched_id;
+                    }
+                }
+            }
+        }
+        
+        $holdings = array();
+        foreach ($item_agency_id as $_agency => $_id) {
+            $request = $this->getStatusRequest(array($_id), null, $_agency);
+            $response = $this->sendRequest($request);
+
+            $bib_id = $response->xpath(
+                    'ns1:Ext/ns1:LookupItemSetResponse/ns1:BibInformation/' .
+                    'ns1:BibliographicId/ns1:BibliographicRecordId/' .
+                    'ns1:BibliographicRecordIdentifier'
+            );
+            
+            $holdingSets = $response->xpath('//ns1:HoldingsSet');
+            
+            foreach ($holdingSets as $holding) {
+                $holdCallNo = $holding->xpath('ns1:CallNumber');
+                $holdCallNo = (string)$holdCallNo[0];
+                $avail = $holding->xpath('ns1:ItemInformation');
+
+                // Build the array of holdings:
+                foreach ($avail as $current) {
+                    $chunk = $this->getHoldingsForChunk($current, $aggregate_id, (string)$bib_id[0]);
+                    $chunk['callnumber'] = empty($chunk['callnumber']) ? 
+                        $holdCallNo : $chunk['callnumber'];
+                    $holdings[] = $chunk;
+                }
+            }
+
+        }
+
+        return $holdings;
+    }
 
     /**
      * Get Holding
@@ -314,20 +562,21 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
      */
     public function getHolding($id, array $patron = null)
     {
-        $request = $this->getStatusRequest(array($id));
-        $response = $this->sendRequest($request);
-        $avail = $response->xpath(
-            'ns1:Ext/ns1:LookupItemSetResponse/ns1:BibInformation'
-        );
-
-        // Build the array of holdings:
-        $holdings = array();
-        foreach ($avail as $current) {
-            $holdings[] = $this->getHoldingsForChunk($current);
+        $ids = null;
+        if (! $this->consortium) {
+            // Translate $id into consortial (035$a) format, e.g., "123" -> "(Agency) 123"
+            $sourceRecord = '';
+            foreach ($this->agency as $_agency => $_dummy) {
+               $sourceRecord = '(' . $_agency . ') ';
+            }
+            $sourceRecord .= $id;
+            $ids = array($sourceRecord);
         }
-        return $holdings;
+
+        return $this->getConsortialHolding($id, $patron, $ids);
     }
 
+
     /**
      * Get Purchase History
      *
@@ -346,49 +595,6 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         return array();
     }
 
-    /**
-     * Build the request XML to log in a user:
-     *
-     * @param string $username Username for login
-     * @param string $password Password for login
-     * @param string $extras   Extra elements to include in the request
-     *
-     * @return string          NCIP request XML
-     */
-    protected function getLookupUserRequest($username, $password, $extras = array())
-    {
-        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' .
-            '<ns1:NCIPMessage xmlns:ns1="http://www.niso.org/2008/ncip" ' .
-            'ns1:version="http://www.niso.org/schemas/ncip/v2_0/imp1/' .
-            'xsd/ncip_v2_0.xsd">' .
-                '<ns1:LookupUser>' .
-                    '<ns1:AuthenticationInput>' .
-                        '<ns1:AuthenticationInputData>' .
-                            htmlspecialchars($username) .
-                        '</ns1:AuthenticationInputData>' .
-                        '<ns1:AuthenticationDataFormatType>' .
-                            'text' .
-                        '</ns1:AuthenticationDataFormatType>' .
-                        '<ns1:AuthenticationInputType>' .
-                            'Username' .
-                        '</ns1:AuthenticationInputType>' .
-                    '</ns1:AuthenticationInput>' .
-                    '<ns1:AuthenticationInput>' .
-                        '<ns1:AuthenticationInputData>' .
-                            htmlspecialchars($password) .
-                        '</ns1:AuthenticationInputData>' .
-                        '<ns1:AuthenticationDataFormatType>' .
-                            'text' .
-                        '</ns1:AuthenticationDataFormatType>' .
-                        '<ns1:AuthenticationInputType>' .
-                            'Password' .
-                        '</ns1:AuthenticationInputType>' .
-                    '</ns1:AuthenticationInput>' .
-                    implode('', $extras) .
-                '</ns1:LookupUser>' .
-            '</ns1:NCIPMessage>';
-    }
-
     /**
      * Patron Login
      *
@@ -408,10 +614,14 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         $id = $response->xpath(
             'ns1:LookupUserResponse/ns1:UserId/ns1:UserIdentifierValue'
         );
+        $patron_agency_id = $response->xpath(
+            'ns1:LookupUserResponse/ns1:UserId/ns1:AgencyId'
+        );
         if (!empty($id)) {
             // Fill in basic patron details:
             $patron = array(
                 'id' => (string)$id[0],
+                'patron_agency_id' => (string)$patron_agency_id[0],
                 'cat_username' => $username,
                 'cat_password' => $password,
                 'email' => null,
@@ -444,7 +654,7 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
      * @return array        Array of the patron's transactions on success.
      */
     public function getMyTransactions($patron)
-    {
+    {        
         $extras = array('<ns1:LoanedItemsDesired/>');
         $request = $this->getLookupUserRequest(
             $patron['cat_username'], $patron['cat_password'], $extras
@@ -453,16 +663,32 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
 
         $retVal = array();
         $list = $response->xpath('ns1:LookupUserResponse/ns1:LoanedItem');
+        
         foreach ($list as $current) {
-            $due = $current->xpath('ns1:DateDue');
+            $current->registerXPathNamespace('ns1', 'http://www.niso.org/2008/ncip');
+            $tmp = $current->xpath('ns1:DateDue');
+            $due = strtotime((string)$tmp[0]);
+            $due = date("l, d-M-y h:i a", $due);
             $title = $current->xpath('ns1:Title');
+            $item_id = $current->xpath('ns1:ItemId/ns1:ItemIdentifierValue');
+            $bib_id = $current->xpath('ns1:Ext/ns1:BibliographicDescription/' .
+                'ns1:BibliographicRecordId/ns1:BibliographicRecordIdentifier');
+            // Hack to account for bibs from other non-local institutions
+            // temporarily until consortial functionality is enabled.
+            if ((string)$bib_id[0]) {
+                $tmp = (string)$bib_id[0];
+            } else {
+                $tmp = "1";
+            }
             $retVal[] = array(
-                'id' => false,
-                'duedate' => (string)$due[0],
-                'title' => (string)$title[0]
+                'id' => $tmp,
+                'duedate' => $due,
+                'title' => (string)$title[0],
+                'item_id' => (string)$item_id[0],
+                'renewable' => true,
             );
-        }
-
+        } 
+        
         return $retVal;
     }
 
@@ -490,7 +716,11 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         );
 
         $fines = array();
+        $balance = 0;
         foreach ($list as $current) {
+
+            $current->registerXPathNamespace('ns1', 'http://www.niso.org/2008/ncip');
+             
             $tmp = $current->xpath(
                 'ns1:FiscalTransactionInformation/ns1:Amount/ns1:MonetaryValue'
             );
@@ -509,9 +739,10 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
             $id = (string)$tmp[0];
              */
             $id = '';
+            $balance += $amount;
             $fines[] = array(
                 'amount' => $amount,
-                'balance' => $amount,
+                'balance' => $balance,
                 'checkout' => '',
                 'fine' => $desc,
                 'duedate' => '',
@@ -544,16 +775,33 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         $retVal = array();
         $list = $response->xpath('ns1:LookupUserResponse/ns1:RequestedItem');
         foreach ($list as $current) {
+            $id = $current->xpath('ns1:Ext/ns1:BibliographicDescription/' .
+                    'ns1:BibliographicRecordId/ns1:BibliographicRecordIdentifier');
             $created = $current->xpath('ns1:DatePlaced');
             $title = $current->xpath('ns1:Title');
             $pos = $current->xpath('ns1:HoldQueuePosition');
-            $retVal[] = array(
-                'id' => false,
-                'create' => (string)$created[0],
-                'expire' => '',
-                'title' => (string)$title[0],
-                'position' => (string)$pos[0]
-            );
+            $requestType = $current->xpath('ns1:RequestType');
+            $requestId = $current->xpath('ns1:RequestId/ns1:RequestIdentifierValue');
+            $itemId = $current->xpath('ns1:ItemId/ns1:ItemIdentifierValue');
+            $pickupLocation = $current->xpath('ns1:PickupLocation');
+            $expireDate = $current->xpath('ns1:PickupExpiryDate');
+            $expireDate = strtotime((string)$expireDate[0]);
+            $expireDate = date("l, d-M-y", $expireDate);
+            $requestType = (string)$requestType[0];
+            // Only return requests of type Hold or Recall. Callslips/Stack
+            // Retrieval requests are fetched using getMyStorageRetrievalRequests
+            if ($requestType === "Hold" or $requestType === "Recall") {
+                $retVal[] = array(
+                    'id' => (string)$id[0],
+                    'create' => '',
+                    'expire' => $expireDate,
+                    'title' => (string)$title[0],
+                    'position' => (string)$pos[0],
+                    'requestId' => (string)$requestId[0],
+                    'item_id' => (string)$itemId[0],
+                    'location' => (string)$pickupLocation[0],
+                );
+            }
         }
 
         return $retVal;
@@ -728,4 +976,636 @@ class XCNCIP2 extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterf
         // TODO
         return array();
     }
+
+    /**
+     * Public Function which retrieves Holds, StorageRetrivalRequests, and Consortial settings from the
+     * driver ini file.
+     *
+     * @param string $function The name of the feature to be checked
+     *
+     * @return array An array with key-value pairs.
+     */
+    public function getConfig($function)
+    {
+        if ($function == 'Holds') {
+            return array(
+                'HMACKeys' => 'item_id:holdtype:item_agency_id:aggregate_id:bib_id',
+                'extraHoldFields' => 'comments:pickUpLocation:requiredByDate',
+                'defaultRequiredDate' => '0:2:0',
+                'consortium' => $this->consortium,
+            );
+        }
+        if ($function == 'StorageRetrievalRequests') {
+            return array(
+                'HMACKeys' => 'id:item_id:item_agency_id:aggregate_id:bib_id',
+                'extraFields' => 'comments:pickUpLocation:requiredByDate:item-issue',
+                'helpText' => 'This is a storage retrieval request help text' .
+                    ' with some <span style="color: red">styling</span>.',
+                'defaultRequiredDate' => '0:2:0',
+            );
+        }
+        return array();
+    }
+    
+    public function getDefaultPickUpLocation($patron = false, $holdDetails = null)
+    {
+        return $this->pickupLocations[$patron['patron_agency_id']][0]['locationID'];
+    }
+
+    public function getPickUpLocations($patron) 
+    {
+        $locations = array();
+        foreach ($this->agency as $agency => $agencyID) {
+            foreach ($this->pickupLocations[$agency] as $thisAgency) {
+                $locations[] = 
+                    array(
+                        'locationID' => $thisAgency['locationID'],
+                        'locationDisplay' => $thisAgency['locationDisplay'],
+                    );
+            }
+        }
+        return $locations;
+    }
+    
+    /**
+     * Get Patron Storage Retrieval Requests
+     *
+     * This is responsible for retrieving all call slips by a specific patron.
+     *
+     * @param array $patron The patron array from patronLogin
+     *
+     * @return array        Array of the patron's storage retrieval requests.
+     */
+    public function getMyStorageRetrievalRequests($patron = false) 
+    {
+        $extras = array('<ns1:RequestedItemsDesired/>');
+        $request = $this->getLookupUserRequest(
+            $patron['cat_username'], $patron['cat_password'], $extras
+        );
+        $response = $this->sendRequest($request);
+
+        $retVal = array();
+        $list = $response->xpath('ns1:LookupUserResponse/ns1:RequestedItem');
+        foreach ($list as $current) {
+            $cancelled = true;
+            $id = $current->xpath('ns1:Ext/ns1:BibliographicDescription/' .
+                    'ns1:BibliographicRecordId/ns1:BibliographicRecordIdentifier');
+            //$created = $current->xpath('ns1:DatePlaced');
+            $title = $current->xpath('ns1:Title');
+            $pos = $current->xpath('ns1:HoldQueuePosition');
+            $pickupLocation = $current->xpath('ns1:PickupLocation');
+            $requestId = $current->xpath('ns1:RequestId/ns1:RequestIdentifierValue');
+            $requestType = $current->xpath('ns1:RequestType');
+            $requestType = (string)$requestType[0];
+            $tmpStatus = $current->xpath('ns1:RequestStatusType');
+            list($status, $created) = explode(" ", (string)$tmpStatus[0], 2);
+            if ($status === "Accepted") {
+                $cancelled = false;
+            }
+            // Only return requests of type Stack Retrieval/Callslip. Hold
+            // and Recall requests are fetched using getMyHolds
+            if ($requestType === 'Stack Retrieval')
+            {
+                $retVal[] = array(
+                    'id' => (string)$id[0],
+                    'create' => $created,
+                    'expire' => '',
+                    'title' => (string)$title[0],
+                    'position' => (string)$pos[0], 
+                    'requestId' => (string)$requestId[0],
+                    'location' => 'test',
+                    'canceled' => $cancelled,
+                    'location' => (string)$pickupLocation[0],
+                    'processed' => false,
+                );
+            }
+        }
+
+        return $retVal;
+    }
+
+    public function checkStorageRetrievalRequestIsValid($id, $data, $patron)
+    {
+        return true;
+    }
+
+    /**
+     * Place Storage Retrieval Request (Call Slip)
+     *
+     * Attempts to place a call slip request on a particular item and returns
+     * an array with result details
+     *
+     * @param array $details An array of item and patron data
+     *
+     * @return mixed An array of data on the request including
+     * whether or not it was successful.
+     */
+    public function placeStorageRetrievalRequest($details)
+    {
+        $username = $details['patron']['cat_username'];
+        $password = $details['patron']['cat_password'];
+        $bibId = $details['bib_id'];
+        $itemId = $details['item_id'];
+        $pickUpLocation = $details['pickUpLocation'];
+        $lastInterestDate = $details['requiredBy'];
+        $lastInterestDate = substr($lastInterestDate, 6, 10) . '-' .
+                substr($lastInterestDate, 0, 5);
+        $lastInterestDate = $lastInterestDate . "T00:00:00.000Z";
+
+        $request = $this->getRequest($username, $password, $bibId, $itemId, 
+                $details['patron']['patron_agency_id'], 
+                $details['patron']['patron_agency_id'], 
+                $details['item_agency_id'], 
+                "Stack Retrieval", "Item", $lastInterestDate, $pickUpLocation);
+
+        $response = $this->sendRequest($request);
+        $success = $response->xpath(
+                'ns1:RequestItemResponse/ns1:ItemId/ns1:ItemIdentifierValue');
+
+        if ($success) {
+            return array(
+                    'success' => true, 
+                    "sysMessage" => 'Storage Retrieval Request Successful.'
+            );
+        } else {
+            return array(
+                    'success' => false, 
+                    "sysMessage" => 'Storage Retrieval Request Not Successful.'
+            );
+        }
+    }
+
+    /**
+     * Get Renew Details
+     *
+     * This function returns the item id as a string which is then used
+     * as submitted form data in checkedOut.php. This value is then extracted by
+     * the RenewMyItems function.
+     *
+     * @param array $checkOutDetails An array of item data
+     *
+     * @return string Data for use in a form field
+     */
+    public function getRenewDetails($checkOutDetails)
+    {
+        $renewDetails = $checkOutDetails['item_id'];
+        return $renewDetails;
+    }
+
+    /**
+     * Place Hold
+     *
+     * Attempts to place a hold or recall on a particular item and returns
+     * an array with result details or throws an exception on failure of support
+     * classes
+     *
+     * @param array $holdDetails An array of item and patron data
+     *
+     * @throws ILSException
+     * @return mixed An array of data on the request including
+     * whether or not it was successful
+     */
+    public function placeHold($details) 
+    {
+        $username = $details['patron']['cat_username'];
+        $password = $details['patron']['cat_password'];
+        $bibId = $details['bib_id'];
+        $itemId = $details['item_id'];
+        $pickUpLocation = $details['pickUpLocation'];
+        $holdType = $details['holdtype'];
+        $lastInterestDate = $details['requiredBy'];
+        $lastInterestDate = substr($lastInterestDate, 6, 10) . '-' .
+                 substr($lastInterestDate, 0, 5);
+        $lastInterestDate = $lastInterestDate . "T00:00:00.000Z";
+       
+        $request = $this->getRequest($username, $password, $bibId, $itemId, 
+                 $details['patron']['patron_agency_id'], 
+                 $details['patron']['patron_agency_id'], 
+                 $details['item_agency_id'], 
+                 $holdType, "Item", $lastInterestDate, $pickUpLocation);
+        $response = $this->sendRequest($request);
+        $success = $response->xpath(
+                'ns1:RequestItemResponse/ns1:ItemId/ns1:ItemIdentifierValue');
+        
+        if ($success) {
+            return array(
+                    'success' => true, 
+                    "sysMessage" => 'Request Successful.'
+            );
+        } else {
+            return array(
+                    'success' => false, 
+                    "sysMessage" => 'Request Not Successful.'
+            );
+        }
+    }
+
+    /**
+     * Cancel Holds
+     *
+     * Attempts to Cancel a hold or recall on a particular item. The
+     * data in $cancelDetails['details'] is determined by getCancelHoldDetails().
+     *
+     * @param array $cancelDetails An array of item and patron data
+     *
+     * @return array               An array of data on each request including
+     * whether or not it was successful.
+     */
+    public function cancelHolds($cancelDetails) 
+    {
+        $count = 0;
+        $username = $cancelDetails['patron']['cat_username'];
+        $password = $cancelDetails['patron']['cat_password'];
+        $details = $cancelDetails['details'];  
+        $response = array();
+
+        foreach ($details as $cancelDetails) {
+            list($itemId, $requestId) = explode("|", $cancelDetails);
+            $request = $this->getCancelRequest(
+                $username, $password, $requestId, "Hold"
+            );
+            $cancelRequestResponse = $this->sendRequest($request);
+            $userId = $cancelRequestResponse->xpath(
+                'ns1:CancelRequestItemResponse/' .
+                'ns1:UserId/ns1:UserIdentifierValue'
+            );
+            $itemId = (string)$itemId;
+            if($userId) {
+                $count++;
+                $response[$itemId] = array(
+                        'success' => true,
+                        'status' => 'hold_cancel_success',
+                );
+            } else {
+                $response[$itemId] = array(
+                        'success' => false,
+                        'status' => 'hold_cancel_fail',
+                );
+            }
+        }
+        $result = array('count' => $count, 'items' => $response);
+        return $result;
+    }
+
+    /**
+     * Get Cancel Hold Details
+     *
+     * This function returns the item id and recall id as a string
+     * separated by a pipe, which is then submitted as form data in Hold.php. This
+     * value is then extracted by the CancelHolds function.  item id is used as the
+     * array key in the response.
+     *
+     * @param array $holdDetails An array of item data
+     *
+     * @return string Data for use in a form field
+     */
+    public function getCancelHoldDetails($holdDetails) 
+    {
+        $cancelDetails = $holdDetails['id']."|".$holdDetails['requestId'];
+        return $cancelDetails;
+    }
+
+    /**
+     * Cancel Storage Retrieval Requests (Call Slips)
+     *
+     * Attempts to Cancel a call slip on a particular item. The
+     * data in $cancelDetails['details'] is determined by
+     * getCancelStorageRetrievalRequestDetails().
+     *
+     * @param array $cancelDetails An array of item and patron data
+     *
+     * @return array               An array of data on each request including
+     * whether or not it was successful.
+     */
+    public function cancelStorageRetrievalRequests($cancelDetails)
+    {
+        $count = 0;
+        $username = $cancelDetails['patron']['cat_username'];
+        $password = $cancelDetails['patron']['cat_password'];
+        $details = $cancelDetails['details'];
+        $response = array();
+
+        foreach ($details as $cancelDetails) {
+            list($itemId, $requestId) = explode("|", $cancelDetails);
+            $request = $this->getCancelRequest(
+                $username, $password, $requestId, "Stack Retrieval"
+            );
+            $cancelRequestResponse = $this->sendRequest($request);
+            $userId = $cancelRequestResponse->xpath(
+                'ns1:CancelRequestItemResponse/'. 
+                'ns1:UserId/ns1:UserIdentifierValue'
+            );
+            $itemId = (string)$itemId;
+            if($userId) {
+                $count++;
+                $response[$itemId] = array(
+                        'success' => true,
+                        'status' => 'storage_retrieval_request_cancel_success',
+                );
+            } else {
+                $response[$itemId] = array(
+                        'success' => false,
+                        'status' => 'storage_retrieval_request_cancel_fail',
+                );
+            }
+        }
+        $result = array('count' => $count, 'items' => $response);
+        return $result;
+    }
+
+    /**
+     * Get Cancel Storage Retrieval Request (Call Slip) Details
+     *
+     * This function returns the item id and call slip id as a
+     * string separated by a pipe, which is then submitted as form data. This
+     * value is then extracted by the CancelStorageRetrievalRequests function. 
+     * The item id is used as the key in the return value.
+     *
+     * @param array $details An array of item data
+     *
+     * @return string Data for use in a form field
+     */
+    public function getCancelStorageRetrievalRequestDetails($callslipDetails)
+    {
+        $cancelDetails = $callslipDetails['id']."|".$callslipDetails['requestId'];
+        return $cancelDetails;
+    }
+
+    /**
+     * Renew My Items
+     *
+     * Function for attempting to renew a patron's items.  The data in
+     * $renewDetails['details'] is determined by getRenewDetails().
+     *
+     * @param array $renewDetails An array of data required for renewing items
+     * including the Patron ID and an array of renewal IDS
+     *
+     * @return array              An array of renewal information keyed by item ID
+     */
+    public function renewMyItems($renewDetails)
+    {
+        $details = array();
+        foreach ($renewDetails['details'] as $renewId) {
+            $request = $this->getRenewRequest(
+                    $renewDetails['patron']['cat_username'], 
+                    $renewDetails['patron']['cat_password'], $renewId);
+            $response = $this->sendRequest($request);
+            $dueDate = $response->xpath('ns1:RenewItemResponse/ns1:DateDue');
+            if ($dueDate) {
+                $tmp = $dueDate;
+                $newDueDate = (string)$tmp[0];
+                $tmp = split("T", $newDueDate);
+                $splitDate = $tmp[0];
+                $splitTime = $tmp[1];
+                $details[$renewId] = array(
+                    "success" => true,
+                    "new_date" => $splitDate,
+                    "new_time" => rtrim($splitTime, "Z"),
+                    "item_id" => $renewId,
+                );
+
+            } else {
+                $details[$renewId] = array(
+                    "success" => false,
+                    "item_id" => $renewId,
+                );
+            }
+        }
+
+        return array(null, "details" => $details);
+    }
+
+    /**
+     * Helper function to build the request XML to cancel a request:
+     *
+     * @param string $username  Username for login
+     * @param string $password  Password for login
+     * @param string $requestId Id of the request to cancel
+     * @param string $type      The type of request to cancel (Hold, etc)
+     *
+     * @return string           NCIP request XML
+     */
+    protected function getCancelRequest($username, $password, $requestId, $type)
+    {
+        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' .
+            '<ns1:NCIPMessage xmlns:ns1="http://www.niso.org/2008/ncip" ' .
+            'ns1:version="http://www.niso.org/schemas/ncip/v2_0/imp1/' .
+            'xsd/ncip_v2_0.xsd">' .
+                '<ns1:CancelRequestItem>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($username) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Username' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($password) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Password' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    '<ns1:RequestId>' .
+                        '<ns1:RequestIdentifierValue>' .
+                            htmlspecialchars($requestId) .
+                        '</ns1:RequestIdentifierValue>' .
+                    '</ns1:RequestId>' .
+                    '<ns1:RequestType>' .
+                        htmlspecialchars($type) .
+                    '</ns1:RequestType>' .
+                '</ns1:CancelRequestItem>' .
+            '</ns1:NCIPMessage>';
+    }
+
+    /**
+     * Helper function to build the request XML to request an item 
+     * (Hold, Storage Retrieval, etc)
+     *
+     * @param string $username         Username for login
+     * @param string $password         Password for login
+     * @param string $bibId            Bib Id of item to request
+     * @param string $itemId           Id of item to request
+     * @param string $requestType      Type of the request (Hold, Callslip, etc)
+     * @param string $requestScope     Level of request (title, item, etc)
+     * @param string $lastInterestDate Last date interested in item
+     * @param string $pickupLocation   Code of location to pickup request
+     *
+     * @return string          NCIP request XML
+     */
+    protected function getRequest($username, $password, $bibId, $itemId,
+            $patron_agency_id, $pickup_agency_id, $item_agency_id,
+            $requestType, $requestScope, $lastInterestDate, $pickupLocation = null)
+    {
+    	return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' .
+            '<ns1:NCIPMessage xmlns:ns1="http://www.niso.org/2008/ncip" ' .
+            'ns1:version="http://www.niso.org/schemas/ncip/v2_0/imp1/' .
+            'xsd/ncip_v2_0.xsd">' .
+                '<ns1:RequestItem>' .
+                   '<ns1:InitiationHeader>' .
+                        '<ns1:FromAgencyId>' .
+                            '<ns1:AgencyId>' .
+                                htmlspecialchars($patron_agency_id) .
+                            '</ns1:AgencyId>' .
+                        '</ns1:FromAgencyId>' .
+                        '<ns1:ToAgencyId>' .
+                            '<ns1:AgencyId>' .
+                                htmlspecialchars($pickup_agency_id) .
+                            '</ns1:AgencyId>' .
+                        '</ns1:ToAgencyId>' .
+                    '</ns1:InitiationHeader>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($username) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Username' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($password) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Password' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    '<ns1:BibliographicId>' .
+                        '<ns1:BibliographicRecordId>' .
+                            '<ns1:AgencyId>' . 
+                                htmlspecialchars($item_agency_id) .
+                            '</ns1:AgencyId>' .
+                            '<ns1:BibliographicRecordIdentifier>' .
+                                htmlspecialchars($bibId) .
+                            '</ns1:BibliographicRecordIdentifier>' .
+                        '</ns1:BibliographicRecordId>' .
+                    '</ns1:BibliographicId>' .
+                    '<ns1:ItemId>' .
+                        '<ns1:ItemIdentifierValue>' .
+                            htmlspecialchars($itemId) .
+                        '</ns1:ItemIdentifierValue>' .
+                    '</ns1:ItemId>' .
+                    '<ns1:RequestType>' .
+                            htmlspecialchars($requestType) .
+                    '</ns1:RequestType>' .
+                    '<ns1:RequestScopeType ' .
+                        'ns1:Scheme="http://www.niso.org/ncip/v1_0/imp1/schemes' .
+                        '/requestscopetype/requestscopetype.scm">' .
+                            htmlspecialchars($requestScope) .
+                    '</ns1:RequestScopeType>' .
+                    '<ns1:PickupLocation>' .
+                        htmlspecialchars($pickupLocation) .
+                    '</ns1:PickupLocation>' .
+                    '<ns1:PickupExpiryDate>' .
+                        htmlspecialchars($lastInterestDate) .
+                    '</ns1:PickupExpiryDate>' .
+                '</ns1:RequestItem>' .
+            '</ns1:NCIPMessage>';
+    }
+    
+    /**
+     * Helper function to build the request XML to renew an item:
+     *
+     * @param string $username Username for login
+     * @param string $password Password for login
+     * @param string $itemId   Id of item to renew
+     *
+     * @return string          NCIP request XML
+     */
+    protected function getRenewRequest($username, $password, $itemId) 
+    {
+        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' .
+            '<ns1:NCIPMessage xmlns:ns1="http://www.niso.org/2008/ncip" ' .
+            'ns1:version="http://www.niso.org/schemas/ncip/v2_0/imp1/' .
+            'xsd/ncip_v2_0.xsd">' .
+                '<ns1:RenewItem>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($username) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Username' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($password) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Password' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    '<ns1:ItemId>' .
+                        '<ns1:ItemIdentifierValue>' .
+                            htmlspecialchars($itemId) .
+                        '</ns1:ItemIdentifierValue>' .
+                    '</ns1:ItemId>' .
+                '</ns1:RenewItem>' .
+            '</ns1:NCIPMessage>';
+    }
+	
+	/**
+     * Helper function to build the request XML to log in a user
+     * and/or retrieve loaned items / request information
+     *
+     * @param string $username Username for login
+     * @param string $password Password for login
+     * @param string $extras   Extra elements to include in the request
+     *
+     * @return string          NCIP request XML
+     */
+    protected function getLookupUserRequest($username, $password, $extras = array())
+    {
+        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' .
+            '<ns1:NCIPMessage xmlns:ns1="http://www.niso.org/2008/ncip" ' .
+            'ns1:version="http://www.niso.org/schemas/ncip/v2_0/imp1/' .
+            'xsd/ncip_v2_0.xsd">' .
+                '<ns1:LookupUser>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($username) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Username' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    '<ns1:AuthenticationInput>' .
+                        '<ns1:AuthenticationInputData>' .
+                            htmlspecialchars($password) .
+                        '</ns1:AuthenticationInputData>' .
+                        '<ns1:AuthenticationDataFormatType>' .
+                            'text' .
+                        '</ns1:AuthenticationDataFormatType>' .
+                        '<ns1:AuthenticationInputType>' .
+                            'Password' .
+                        '</ns1:AuthenticationInputType>' .
+                    '</ns1:AuthenticationInput>' .
+                    implode('', $extras) .
+                '</ns1:LookupUser>' .
+            '</ns1:NCIPMessage>';
+    }
 }
+