/**
  * Loads items information as quickly as possible (no direct calls to the ILS).  Does do filtering by loan rules
  *
  * return is an array of items with the following information:
  *  location
  *  callnumber
  *  available
  *  holdable
  *  lastStatusCheck (time)
  *
  * @param $id
  * @param $scopingEnabled
  * @param $marcRecord
  * @return mixed
  */
 public function getItemsFast($id, $scopingEnabled, $marcRecord = null)
 {
     /** @var Memcache $memCache */
     global $memCache;
     $key = 'items_fast_' . $id . '_' . $scopingEnabled;
     $cachedValue = $memCache->get($key);
     if ($cachedValue == false || isset($_REQUEST['reload'])) {
         global $configArray;
         $cachedValue = $this->driver->getItemsFast($id, $scopingEnabled, $marcRecord);
         $memCache->add($key, $cachedValue, 0, $configArray['Caching']['item_data']);
     }
     return $cachedValue;
 }
 public function renewItem($itemId, $itemIndex)
 {
     global $logger;
     global $configArray;
     global $analytics;
     //Setup the call to Millennium
     $patronDump = $this->driver->_getPatronDump($this->driver->_getBarcode());
     $extraGetInfo = array('currentsortorder' => 'current_checkout', 'renewsome' => 'YES', 'renew' . $itemIndex => $itemId);
     $get_items = array();
     foreach ($extraGetInfo as $key => $value) {
         $get_items[] = $key . '=' . urlencode($value);
     }
     $renewItemParams = implode('&', $get_items);
     //Login to the patron's account
     $cookieJar = tempnam("/tmp", "CURLCOOKIE");
     $curl_url = $configArray['Catalog']['url'] . "/patroninfo";
     //$logger->log('Loading page ' . $curl_url, PEAR_LOG_INFO);
     $curl_connection = curl_init($curl_url);
     curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 30);
     curl_setopt($curl_connection, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
     curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
     curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
     curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, 1);
     curl_setopt($curl_connection, CURLOPT_UNRESTRICTED_AUTH, true);
     curl_setopt($curl_connection, CURLOPT_COOKIEJAR, $cookieJar);
     curl_setopt($curl_connection, CURLOPT_COOKIESESSION, false);
     curl_setopt($curl_connection, CURLOPT_POST, true);
     $post_data = $this->driver->_getLoginFormValues();
     $post_items = array();
     foreach ($post_data as $key => $value) {
         $post_items[] = $key . '=' . urlencode($value);
     }
     $post_string = implode('&', $post_items);
     curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
     curl_exec($curl_connection);
     //Go to the items page
     $scope = $this->driver->getDefaultScope();
     $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/items";
     curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
     curl_setopt($curl_connection, CURLOPT_HTTPGET, true);
     curl_exec($curl_connection);
     //Post renewal information
     $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/items";
     curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
     curl_setopt($curl_connection, CURLOPT_POST, true);
     curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $renewItemParams);
     $checkedOutPageText = curl_exec($curl_connection);
     //Parse the checked out titles into individual rows
     $message = 'Unable to load renewal information for this entry';
     $success = false;
     if (preg_match('/<h2>\\s*You cannot renew items because:\\s*<\\/h2><ul><li>(.*?)<\\/ul>/si', $checkedOutPageText, $matches)) {
         $success = false;
         $message = 'Unable to renew this item, ' . strtolower($matches[1]) . '.';
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Renew Failed', strtolower($matches[1]));
         }
     } else {
         if (preg_match('/Your record is in use/si', $checkedOutPageText, $matches)) {
             $success = false;
             $message = 'Unable to renew this item now, your account is in use by the system.  Please try again later.';
             if ($analytics) {
                 $analytics->addEvent('ILS Integration', 'Renew Failed', 'Account in Use');
             }
         } else {
             if (preg_match('/<table border="0" class="patFunc">(.*?)<\\/table>/s', $checkedOutPageText, $matches)) {
                 $checkedOutTitleTable = $matches[1];
                 //$logger->log("Found checked out titles table", PEAR_LOG_DEBUG);
                 if (preg_match_all('/<tr class="patFuncEntry">(.*?)<\\/tr>/s', $checkedOutTitleTable, $rowMatches, PREG_SET_ORDER)) {
                     //$logger->log("Checked out titles table has " . count($rowMatches) . "rows", PEAR_LOG_DEBUG);
                     //$logger->log(print_r($rowMatches, true), PEAR_LOG_DEBUG);
                     for ($i = 0; $i < count($rowMatches); $i++) {
                         $rowData = $rowMatches[$i][1];
                         if (preg_match("/{$itemId}/", $rowData)) {
                             //$logger->log("Found the row for this item", PEAR_LOG_DEBUG);
                             //Extract the renewal message
                             if (preg_match('/<td align="left" class="patFuncStatus">.*?<em><font color="red">(.*?)<\\/font><\\/em>.*?<\\/td>/s', $rowData, $statusMatches)) {
                                 $success = false;
                                 $message = 'Unable to renew this item, ' . $statusMatches[1];
                             } elseif (preg_match('/<td align="left" class="patFuncStatus">.*?<em>(.*?)<\\/em>.*?<\\/td>/s', $rowData, $statusMatches)) {
                                 $success = true;
                                 $message = 'Your item was successfully renewed';
                             }
                             $logger->log("Renew success = {$success}, {$message}", PEAR_LOG_DEBUG);
                         }
                     }
                 } else {
                     $logger->log("Did not find any rows for the table {$checkedOutTitleTable}", PEAR_LOG_DEBUG);
                 }
             } else {
                 $success = true;
                 $message = 'Your item was successfully renewed';
                 if ($analytics) {
                     $analytics->addEvent('ILS Integration', 'Renew Successful');
                 }
             }
         }
     }
     curl_close($curl_connection);
     unlink($cookieJar);
     return array('itemId' => $itemId, 'result' => $success, 'message' => $message);
 }
Beispiel #3
0
 function placeHold()
 {
     global $interface;
     global $configArray;
     global $user;
     global $logger;
     //TODO: Clean this up so there is only ever one id.
     if (isset($_REQUEST['recordId'])) {
         $recordId = $_REQUEST['recordId'];
     } else {
         $recordId = $_REQUEST['id'];
     }
     $interface->assign('id', $recordId);
     //Get title information for the record.
     $holding = $this->catalog->getHolding($recordId);
     if (PEAR_Singleton::isError($holding)) {
         PEAR_Singleton::raiseError($holding);
     }
     $interface->assign('holding', $holding);
     if (isset($_REQUEST['autologout'])) {
         $_SESSION['autologout'] = true;
     }
     $showMessage = false;
     $type = isset($_REQUEST['holdType']) ? $_REQUEST['holdType'] : '';
     if (isset($_POST['submit']) || $type == 'recall' || $type == 'update' || $type == 'hold') {
         if (isset($_REQUEST['username']) && isset($_REQUEST['password'])) {
             //Log the user in
             $user = UserAccount::login();
         }
         if ($user) {
             //The user is already logged in
             $barcodeProperty = $configArray['Catalog']['barcodeProperty'];
             $return = $this->catalog->placeHold($recordId, $user->{$barcodeProperty}, '', $type);
             $interface->assign('result', $return['result']);
             $message = $return['message'];
             $interface->assign('message', $message);
             $showMessage = true;
         } else {
             $message = 'Incorrect Patron Information';
             $interface->assign('message', $message);
             $interface->assign('focusElementId', 'username');
             $showMessage = true;
         }
     } else {
         //Get the referrer so we can go back there.
         if (isset($_SERVER['HTTP_REFERER'])) {
             $referer = $_SERVER['HTTP_REFERER'];
             $_SESSION['hold_referrer'] = $referer;
         }
         //Showing place hold form.
         if ($user) {
             $profile = $this->catalog->getMyProfile($user);
             $interface->assign('profile', $profile);
             //Get information to show a warning if the user does not have sufficient holds
             require_once ROOT_DIR . '/Drivers/marmot_inc/PType.php';
             $maxHolds = -1;
             //Determine if we should show a warning
             $ptype = new PType();
             $ptype->pType = $user->patronType;
             if ($ptype->find(true)) {
                 $maxHolds = $ptype->maxHolds;
             }
             $currentHolds = $profile['numHolds'];
             if ($maxHolds != -1 && $currentHolds + 1 > $maxHolds) {
                 $interface->assign('showOverHoldLimit', true);
                 $interface->assign('maxHolds', $maxHolds);
                 $interface->assign('currentHolds', $currentHolds);
             }
             global $locationSingleton;
             //Get the list of pickup branch locations for display in the user interface.
             $locations = $locationSingleton->getPickupBranches($profile, $profile['homeLocationId']);
             $interface->assign('pickupLocations', $locations);
             //set focus to the submit button if the user is logged in since the campus will be correct most of the time.
             $interface->assign('focusElementId', 'submit');
         } else {
             //set focus to the username field by default.
             $interface->assign('focusElementId', 'username');
         }
         global $library;
         $patronHomeBranch = Library::getPatronHomeLibrary();
         if ($patronHomeBranch != null) {
             if ($patronHomeBranch->defaultNotNeededAfterDays > 0) {
                 $interface->assign('defaultNotNeededAfterDays', date('m/d/Y', time() + $patronHomeBranch->defaultNotNeededAfterDays * 60 * 60 * 24));
             } else {
                 $interface->assign('defaultNotNeededAfterDays', '');
             }
             $interface->assign('showHoldCancelDate', $patronHomeBranch->showHoldCancelDate);
         } else {
             if ($library) {
                 //Show the hold cancellation date for now.  It may be hidden later when the user logs in.
                 if ($library->defaultNotNeededAfterDays > 0) {
                     $interface->assign('defaultNotNeededAfterDays', date('m/d/Y', time() + $library->defaultNotNeededAfterDays * 60 * 60 * 24));
                 } else {
                     $interface->assign('defaultNotNeededAfterDays', '');
                 }
                 $interface->assign('showHoldCancelDate', $library->showHoldCancelDate);
             } else {
                 //Show the hold cancellation date for now.  It may be hidden later when the user logs in.
                 $interface->assign('showHoldCancelDate', 1);
                 $interface->assign('defaultNotNeededAfterDays', '');
             }
         }
         $activeLibrary = Library::getActiveLibrary();
         if ($activeLibrary != null) {
             $interface->assign('holdDisclaimer', $activeLibrary->holdDisclaimer);
         } else {
             //Show the hold cancellation date for now.  It may be hidden later when the user logs in.
             $interface->assign('holdDisclaimer', '');
         }
     }
     $record = RecordDriverFactory::initRecordDriverById('ils:' . $_GET['id']);
     if ($record) {
         $interface->assign('record', $record);
     } else {
         PEAR_Singleton::raiseError('Cannot find record ' . $_GET['id']);
     }
     $interface->assign('id', $_GET['id']);
     if ($showMessage && isset($return)) {
         $hold_message_data = array('successful' => $return['result'] == true ? 'all' : 'none', 'error' => isset($return['error']) ? $return['error'] : '', 'titles' => array($return), 'campus' => $_REQUEST['campus']);
         //Check to see if there are item level holds that need follow-up by the user
         if (isset($return['items']) && count($return['items']) > 0) {
             $hold_message_data['showItemForm'] = true;
             $hold_message_data['items'] = $return['items'];
         }
         $_SESSION['hold_message'] = $hold_message_data;
         if (isset($_SESSION['hold_referrer'])) {
             $logger->log('Hold Referrer is set, redirecting to there. location ' . $_SESSION['hold_referrer'], PEAR_LOG_INFO);
             if ($_REQUEST['type'] != 'recall' && $_REQUEST['type'] != 'cancel' && $_REQUEST['type'] != 'update') {
                 header("Location: " . $_SESSION['hold_referrer']);
             } else {
                 //Redirect for hold cancellation or update
                 $section = isset($_REQUEST['section']) ? $_REQUEST['section'] : 'unavailable';
                 header("Location: " . '/MyResearch/Holds?section=' . $section);
             }
             if (!isset($hold_message_data['showItemForm']) || $hold_message_data['showItemForm'] == false) {
                 unset($_SESSION['hold_referrer']);
                 if (isset($_SESSION['autologout'])) {
                     unset($_SESSION['autologout']);
                     UserAccount::softLogout();
                 }
             }
         } else {
             $logger->log('No referrer set, but there is a message to show, go to the main holds page', PEAR_LOG_INFO);
             header("Location: " . '/MyResearch/Holds');
             die;
         }
     } else {
         //$logger->log('placeHold finished, do not need to show a message', PEAR_LOG_INFO);
         $interface->setPageTitle('Request an Item');
         $interface->assign('subTemplate', 'hold.tpl');
         $interface->setTemplate('hold.tpl');
         $interface->display('layout.tpl', 'RecordHold' . $_GET['id']);
     }
 }
 private function parseReadingHistoryPage($pageContents, $patron, $sortOption, $recordsRead)
 {
     set_time_limit(60);
     //Get the headers from the table
     preg_match_all('/<th\\s+class="patFuncHeaders">\\s*(.*?)\\s*<\\/th>/si', $pageContents, $result, PREG_SET_ORDER);
     $sKeys = array();
     for ($matchi = 0; $matchi < count($result); $matchi++) {
         $sKeys[] = strip_tags($result[$matchi][1]);
     }
     //Get the rows for the table
     preg_match_all('/<tr\\s+class="patFuncEntry">(.*?)<\\/tr>/si', $pageContents, $result, PREG_SET_ORDER);
     $sRows = array();
     for ($matchi = 0; $matchi < count($result); $matchi++) {
         $sRows[] = $result[$matchi][1];
     }
     $sCount = 1;
     $readingHistoryTitles = array();
     foreach ($sRows as $sRow) {
         preg_match_all('/<td[^>]*>(.*?)<\\/td>/si', $sRow, $result, PREG_SET_ORDER);
         $sCols = array();
         for ($matchi = 0; $matchi < count($result); $matchi++) {
             $sCols[] = $result[$matchi][1];
         }
         $historyEntry = array();
         for ($i = 0; $i < sizeof($sCols); $i++) {
             $sCols[$i] = str_replace("&nbsp;", " ", $sCols[$i]);
             $sCols[$i] = preg_replace("/<br+?>/", " ", $sCols[$i]);
             $sCols[$i] = html_entity_decode(trim($sCols[$i]));
             if (stripos($sKeys[$i], "Mark") > -1) {
                 if (preg_match('/id="rsh(\\d+)"/', $sCols[$i], $matches)) {
                     $itemIndex = $matches[1];
                     $historyEntry['itemindex'] = $itemIndex;
                 }
                 $historyEntry['deletable'] = "BOX";
             }
             if (stripos($sKeys[$i], "Title") > -1) {
                 //echo("Title value is <br/>$sCols[$i]<br/>");
                 if (preg_match('/.*?<a href=\\"\\/record=(.*?)(?:~S\\d{1,2})\\">(.*?)<\\/a>.*/', $sCols[$i], $matches)) {
                     $shortId = $matches[1];
                     $bibId = '.' . $matches[1];
                     $historyEntry['id'] = $bibId;
                     $historyEntry['shortId'] = $shortId;
                 } elseif (preg_match('/.*<a href=".*?\\/record\\/C__R(.*?)\\?.*?">(.*?)<\\/a>.*/si', $sCols[$i], $matches)) {
                     $shortId = $matches[1];
                     $bibId = '.' . $matches[1] . $this->driver->getCheckDigit($shortId);
                     $historyEntry['id'] = $bibId;
                     $historyEntry['shortId'] = $shortId;
                 }
                 $title = strip_tags($sCols[$i]);
                 $historyEntry['title'] = utf8_encode($title);
             }
             if (stripos($sKeys[$i], "Author") > -1) {
                 $historyEntry['author'] = utf8_encode(strip_tags($sCols[$i]));
             }
             if (stripos($sKeys[$i], "Checked Out") > -1) {
                 $historyEntry['checkout'] = strip_tags($sCols[$i]);
             }
             if (stripos($sKeys[$i], "Details") > -1) {
                 $historyEntry['details'] = strip_tags($sCols[$i]);
             }
             if (is_array($patron)) {
                 $historyEntry['borrower_num'] = $patron['id'];
             } else {
                 $historyEntry['borrower_num'] = $patron->id;
             }
         }
         //Done processing row
         $historyEntry['title_sort'] = preg_replace('/[^a-z\\s]/', '', strtolower($historyEntry['title']));
         //$historyEntry['itemindex'] = $itemindex++;
         if ($sortOption == "title") {
             $titleKey = $historyEntry['title_sort'];
         } elseif ($sortOption == "author") {
             $titleKey = $historyEntry['author'] . "_" . $historyEntry['title_sort'];
         } elseif ($sortOption == "checkedOut" || $sortOption == "returned") {
             $checkoutTime = DateTime::createFromFormat('m-d-Y', $historyEntry['checkout']);
             if ($checkoutTime) {
                 $titleKey = $checkoutTime->getTimestamp() . "_" . $historyEntry['title_sort'];
             } else {
                 //print_r($historyEntry);
                 $titleKey = $historyEntry['title_sort'];
             }
         } elseif ($sortOption == "format") {
             $titleKey = $historyEntry['format'] . "_" . $historyEntry['title_sort'];
         } else {
             $titleKey = $historyEntry['title_sort'];
         }
         $titleKey .= '_' . ($sCount + $recordsRead);
         $readingHistoryTitles[$titleKey] = $historyEntry;
         $sCount++;
     }
     //processed all rows in the table
     return $readingHistoryTitles;
 }
Beispiel #5
0
function loadSearchInformation()
{
    //Determine the Search Source, need to do this always.
    global $searchSource;
    global $library;
    global $interface;
    $module = isset($_GET['module']) ? $_GET['module'] : null;
    $module = preg_replace('/[^\\w]/', '', $module);
    $searchSource = 'global';
    if (isset($_GET['searchSource'])) {
        $searchSource = $_GET['searchSource'];
        $_REQUEST['searchSource'] = $searchSource;
        //Update request since other check for it here
        $_SESSION['searchSource'] = $searchSource;
        //Update the session so we can remember what the user was doing last.
    } else {
        if (isset($_SESSION['searchSource'])) {
            //Didn't get a source, use what the user was doing last
            $searchSource = $_SESSION['searchSource'];
            $_REQUEST['searchSource'] = $searchSource;
        } else {
            //Use a default search source
            if ($module == 'Person') {
                $searchSource = 'genealogy';
            } else {
                $searchSource = 'local';
            }
            $_REQUEST['searchSource'] = $searchSource;
        }
    }
    $searchLibrary = Library::getSearchLibrary(null);
    $searchLocation = Location::getSearchLocation(null);
    //Based on the search source, determine the search scope and set a global variable
    global $solrScope;
    global $scopeType;
    $solrScope = false;
    $scopeType = '';
    if ($searchSource == 'local' || $searchSource == 'econtent') {
        $locationIsScoped = $searchLocation != null && ($searchLocation->restrictSearchByLocation || $searchLocation->econtentLocationsToInclude != 'all' || $searchLocation->useScope || !$searchLocation->enableOverdriveCollection || strlen($searchLocation->extraLocationCodesToInclude) > 0);
        $libraryIsScoped = $searchLibrary != null && ($searchLibrary->restrictSearchByLibrary || $searchLibrary->econtentLocationsToInclude != 'all' || strlen($searchLibrary->pTypes) > 0 && $searchLibrary->pTypes != -1 || $searchLibrary->useScope || !$searchLibrary->enableOverdriveCollection);
        if ($locationIsScoped && ($searchLocation->econtentLocationsToInclude != $searchLibrary->econtentLocationsToInclude && strlen($searchLocation->econtentLocationsToInclude) > 0 && $searchLocation->econtentLocationsToInclude != 'all' || $searchLocation->useScope && $searchLibrary->scope != $searchLocation->scope)) {
            $solrScope = $searchLocation->code;
            $scopeType = 'Location';
        } else {
            $solrScope = $searchLibrary->subdomain;
            $scopeType = 'Library';
        }
    } elseif ($searchSource != 'marmot' && $searchSource != 'global') {
        $solrScope = $searchSource;
        $scopeType = 'Search Source';
    }
    $solrScope = trim($solrScope);
    if (strlen($solrScope) == 0) {
        $solrScope = false;
        $scopeType = 'Unscoped';
    }
    $searchLibrary = Library::getSearchLibrary($searchSource);
    $searchLocation = Location::getSearchLocation($searchSource);
    global $millenniumScope;
    if ($library) {
        if ($searchLibrary) {
            $millenniumScope = $searchLibrary->scope;
        } elseif (isset($searchLocation)) {
            MillenniumDriver::$scopingLocationCode = $searchLocation->code;
        } else {
            $millenniumScope = isset($configArray['OPAC']['defaultScope']) ? $configArray['OPAC']['defaultScope'] : '93';
        }
    } else {
        $millenniumScope = isset($configArray['OPAC']['defaultScope']) ? $configArray['OPAC']['defaultScope'] : '93';
    }
}
 /**
  * Place Item Hold
  *
  * This is responsible for both placing item level holds.
  *
  * @param   string  $recordId   The id of the bib record
  * @param   string  $itemId     The id of the item to hold
  * @param   string  $patronId   The id of the patron
  * @param   string  $comment    Any comment regarding the hold or recall
  * @param   string  $type       Whether to place a hold or recall
  * @param   string  $type       The date when the hold should be cancelled if any
  * @return  mixed               True if successful, false if unsuccessful
  *                              If an error occurs, return a PEAR_Error
  * @access  public
  */
 public function placeItemHold($recordId, $itemId, $patronId, $comment, $type)
 {
     global $configArray;
     $bib1 = $recordId;
     if (substr($bib1, 0, 1) != '.') {
         $bib1 = '.' . $bib1;
     }
     $bib = substr(str_replace('.b', 'b', $bib1), 0, -1);
     if (strlen($bib) == 0) {
         return array('result' => false, 'message' => 'A valid record id was not provided. Please try again.');
     }
     //Get the title of the book.
     $class = $configArray['Index']['engine'];
     $url = $configArray['Index']['url'];
     $this->driver->db = new $class($url);
     // Retrieve Full Marc Record
     require_once ROOT_DIR . '/RecordDrivers/Factory.php';
     $record = RecordDriverFactory::initRecordDriverById('ils:' . $bib1);
     if (!$record) {
         $title = null;
     } else {
         $title = $record->getTitle();
     }
     if ($configArray['Catalog']['offline']) {
         global $user;
         require_once ROOT_DIR . '/sys/OfflineHold.php';
         $offlineHold = new OfflineHold();
         $offlineHold->bibId = $bib1;
         $offlineHold->patronBarcode = $patronId;
         $offlineHold->patronId = $user->id;
         $offlineHold->timeEntered = time();
         $offlineHold->status = 'Not Processed';
         if ($offlineHold->insert()) {
             return array('title' => $title, 'bib' => $bib1, 'result' => true, 'message' => 'The circulation system is currently offline.  This hold will be entered for you automatically when the circulation system is online.');
         } else {
             return array('title' => $title, 'bib' => $bib1, 'result' => false, 'message' => 'The circulation system is currently offline and we could not place this hold.  Please try again later.');
         }
     } else {
         //Cancel a hold
         if ($type == 'cancel' || $type == 'recall' || $type == 'update') {
             $result = $this->updateHold($recordId, $patronId, $type, $title);
             $result['title'] = $title;
             $result['bid'] = $bib1;
             return $result;
         } else {
             if (isset($_REQUEST['canceldate']) && !is_null($_REQUEST['canceldate']) && $_REQUEST['canceldate'] != '') {
                 $date = $_REQUEST['canceldate'];
             } else {
                 //Default to a date 6 months (half a year) in the future.
                 $sixMonthsFromNow = time() + 182.5 * 24 * 60 * 60;
                 $date = date('m/d/Y', $sixMonthsFromNow);
             }
             if (isset($_REQUEST['campus'])) {
                 $campus = trim($_REQUEST['campus']);
             } else {
                 global $user;
                 $campus = $user->homeLocationId;
             }
             if (is_numeric($campus)) {
                 $location = new Location();
                 $location->locationId = $campus;
                 if ($location->find(true)) {
                     $campus = $location->code;
                 }
             }
             list($Month, $Day, $Year) = explode("/", $date);
             //------------BEGIN CURL-----------------------------------------------------------------
             $header = array();
             $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
             $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
             $header[] = "Cache-Control: max-age=0";
             $header[] = "Connection: keep-alive";
             $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
             $header[] = "Accept-Language: en-us,en;q=0.5";
             $cookie = tempnam("/tmp", "CURLCOOKIE");
             $curl_connection = curl_init();
             curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 30);
             curl_setopt($curl_connection, CURLOPT_HTTPHEADER, $header);
             curl_setopt($curl_connection, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
             curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
             curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
             curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, true);
             curl_setopt($curl_connection, CURLOPT_UNRESTRICTED_AUTH, true);
             curl_setopt($curl_connection, CURLOPT_COOKIEJAR, $cookie);
             curl_setopt($curl_connection, CURLOPT_COOKIESESSION, true);
             curl_setopt($curl_connection, CURLOPT_FORBID_REUSE, false);
             curl_setopt($curl_connection, CURLOPT_HEADER, false);
             curl_setopt($curl_connection, CURLOPT_POST, true);
             $lt = null;
             if (isset($configArray['Catalog']['loginPriorToPlacingHolds']) && ($configArray['Catalog']['loginPriorToPlacingHolds'] = true)) {
                 //User must be logged in as a separate step to placing holds
                 $curl_url = $configArray['Catalog']['url'] . "/patroninfo";
                 $post_data = $this->driver->_getLoginFormValues();
                 $post_data['submit.x'] = "35";
                 $post_data['submit.y'] = "21";
                 $post_data['submit'] = "submit";
                 curl_setopt($curl_connection, CURLOPT_REFERER, $curl_url);
                 curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
                 $post_items = array();
                 foreach ($post_data as $key => $value) {
                     $post_items[] = $key . '=' . $value;
                 }
                 $post_string = implode('&', $post_items);
                 curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
                 $loginResult = curl_exec($curl_connection);
                 $curlInfo = curl_getinfo($curl_connection);
                 //When a library uses Encore, the initial login does a redirect and requires additional parameters.
                 if (preg_match('/<input type="hidden" name="lt" value="(.*?)" \\/>/si', $loginResult, $loginMatches)) {
                     //Get the lt value
                     $lt = $loginMatches[1];
                     //Login again
                     $post_data['lt'] = $lt;
                     $post_data['_eventId'] = 'submit';
                     $post_items = array();
                     foreach ($post_data as $key => $value) {
                         $post_items[] = $key . '=' . $value;
                     }
                     $post_string = implode('&', $post_items);
                     curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
                     $loginResult = curl_exec($curl_connection);
                     $curlInfo = curl_getinfo($curl_connection);
                 }
                 $post_data = array();
             } else {
                 $post_data = $this->driver->_getLoginFormValues();
             }
             $scope = $this->driver->getLibraryScope();
             $curl_url = $configArray['Catalog']['url'] . "/search/.{$bib}/.{$bib}/1,1,1,B/request~{$bib}";
             //echo "$curl_url";
             curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
             /** @var Library $librarySingleton */
             global $librarySingleton;
             $patronHomeBranch = $librarySingleton->getPatronHomeLibrary();
             if ($patronHomeBranch->defaultNotNeededAfterDays != -1) {
                 $post_data['needby_Month'] = $Month;
                 $post_data['needby_Day'] = $Day;
                 $post_data['needby_Year'] = $Year;
             }
             $post_data['submit.x'] = "35";
             $post_data['submit.y'] = "21";
             $post_data['submit'] = "submit";
             $post_data['locx00'] = str_pad($campus, 5 - strlen($campus), '+');
             if (!is_null($itemId) && $itemId != -1) {
                 $post_data['radio'] = $itemId;
             }
             $post_data['x'] = "48";
             $post_data['y'] = "15";
             if ($lt != null) {
                 $post_data['lt'] = $lt;
                 $post_data['_eventId'] = 'submit';
             }
             $post_items = array();
             foreach ($post_data as $key => $value) {
                 $post_items[] = $key . '=' . $value;
             }
             $post_string = implode('&', $post_items);
             curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
             $sResult = curl_exec($curl_connection);
             global $logger;
             $logger->log("Placing hold {$curl_url}?{$post_string}", PEAR_LOG_INFO);
             $sResult = preg_replace("/<!--([^(-->)]*)-->/", "", $sResult);
             curl_close($curl_connection);
             //Parse the response to get the status message
             $hold_result = $this->_getHoldResult($sResult);
             $hold_result['title'] = $title;
             $hold_result['bid'] = $bib1;
             global $analytics;
             if ($analytics) {
                 if ($hold_result['result'] == true) {
                     $analytics->addEvent('ILS Integration', 'Successful Hold', $title);
                 } else {
                     $analytics->addEvent('ILS Integration', 'Failed Hold', $hold_result['message'] . ' - ' . $title);
                 }
             }
             //Clear the patron profile
             $this->driver->clearPatronProfile();
             return $hold_result;
         }
     }
 }
 public function renewItem($itemId, $itemIndex)
 {
     global $logger;
     global $configArray;
     global $analytics;
     //Setup the call to Millennium
     $patronDump = $this->driver->_getPatronDump($this->driver->_getBarcode());
     $extraGetInfo = array('currentsortorder' => 'current_checkout', 'renewsome' => 'YES', 'renew' . $itemIndex => $itemId);
     $renewItemParams = http_build_query($extraGetInfo);
     //Login to the patron's account
     $cookieJar = tempnam("/tmp", "CURLCOOKIE");
     $curl_url = $configArray['Catalog']['url'] . "/patroninfo";
     //$logger->log('Loading page ' . $curl_url, PEAR_LOG_INFO);
     $curl_connection = curl_init($curl_url);
     curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 30);
     curl_setopt($curl_connection, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
     curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
     curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
     curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, 1);
     curl_setopt($curl_connection, CURLOPT_UNRESTRICTED_AUTH, true);
     curl_setopt($curl_connection, CURLOPT_COOKIEJAR, $cookieJar);
     curl_setopt($curl_connection, CURLOPT_COOKIESESSION, false);
     curl_setopt($curl_connection, CURLOPT_POST, true);
     $post_data = $this->driver->_getLoginFormValues();
     $post_string = http_build_query($post_data);
     curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
     $loginResult = curl_exec($curl_connection);
     //When a library uses Encore, the initial login does a redirect and requires additional parameters.
     if (preg_match('/<input type="hidden" name="lt" value="(.*?)" \\/>/si', $loginResult, $loginMatches)) {
         //Get the lt value
         $lt = $loginMatches[1];
         //Login again
         $post_data['lt'] = $lt;
         $post_data['_eventId'] = 'submit';
         $post_string = http_build_query($post_data);
         curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
         $loginResult = curl_exec($curl_connection);
         $curlInfo = curl_getinfo($curl_connection);
     }
     //Go to the items page
     $scope = $this->driver->getDefaultScope();
     $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/items";
     curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
     curl_setopt($curl_connection, CURLOPT_HTTPGET, true);
     curl_exec($curl_connection);
     //Post renewal information
     $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/items";
     curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
     curl_setopt($curl_connection, CURLOPT_POST, true);
     curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $renewItemParams);
     $checkedOutPageText = curl_exec($curl_connection);
     //Parse the checked out titles into individual rows
     $message = 'Unable to load renewal information for this entry.';
     $success = false;
     if (preg_match('/<h2>\\s*You cannot renew items because:\\s*<\\/h2><ul><li>(.*?)<\\/ul>/si', $checkedOutPageText, $matches)) {
         $success = false;
         $msg = ucfirst(strtolower(trim($matches[1])));
         $message = "Unable to renew this item: {$msg}.";
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Renew Failed', $msg);
         }
     } elseif (preg_match('/Your record is in use/si', $checkedOutPageText, $matches)) {
         $success = false;
         $message = 'Unable to renew this item now, your account is in use by the system.  Please try again later.';
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Renew Failed', 'Account in Use');
         }
     } elseif (preg_match('/<table border="0" class="patFunc">(.*?)<\\/table>/s', $checkedOutPageText, $matches)) {
         $checkedOutTitleTable = $matches[1];
         //$logger->log("Found checked out titles table", PEAR_LOG_DEBUG);
         if (preg_match_all('/<tr class="patFuncEntry">(.*?)<\\/tr>/s', $checkedOutTitleTable, $rowMatches, PREG_SET_ORDER)) {
             //$logger->log("Checked out titles table has " . count($rowMatches) . "rows", PEAR_LOG_DEBUG);
             //$logger->log(print_r($rowMatches, true), PEAR_LOG_DEBUG);
             //				for ($i = 0; $i < count($rowMatches); $i++) {
             foreach ($rowMatches as $i => $row) {
                 $rowData = $row[1];
                 if (preg_match("/{$itemId}/", $rowData)) {
                     //$logger->log("Found the row for this item", PEAR_LOG_DEBUG);
                     //Extract the renewal message
                     if (preg_match('/<td align="left" class="patFuncStatus">.*?<em><font color="red">(.*?)<\\/font><\\/em>.*?<\\/td>/s', $rowData, $statusMatches)) {
                         $success = false;
                         $msg = ucfirst(strtolower(trim($statusMatches[1])));
                         $title = $this->extract_title_from_row($rowData);
                         $message = "Unable to renew {$title}: {$msg}.";
                         // title needed for in renewSelectedItems to distinguish which item failed.
                     } elseif (preg_match('/<td align="left" class="patFuncStatus">.*?<em>(.*?)<\\/em>.*?<\\/td>/s', $rowData, $statusMatches)) {
                         $success = true;
                         $message = 'Your item was successfully renewed';
                     }
                     $logger->log("Renew success = {$success}, {$message}", PEAR_LOG_DEBUG);
                     break;
                     // found our item, get out of loop.
                 }
             }
         } else {
             $logger->log("Did not find any rows for the table {$checkedOutTitleTable}", PEAR_LOG_DEBUG);
         }
     } else {
         $success = true;
         $message = 'Your item was successfully renewed';
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Renew Successful');
         }
     }
     curl_close($curl_connection);
     unlink($cookieJar);
     return array('itemId' => $itemId, 'result' => $success, 'message' => $message);
 }
 /**
  * Get suppressed records.
  *
  * @return array ID numbers of suppressed records in the system.
  * @access public
  */
 public function getSuppressedRecords()
 {
     return $this->driver->getSuppressedRecords();
 }
Beispiel #9
0
 /**
  * Loads items information as quickly as possible (no direct calls to the ILS).  Does do filtering by loan rules
  *
  * return is an array of items with the following information:
  *  location
  *  callnumber
  *  available
  *  holdable
  *  lastStatusCheck (time)
  *
  * @param $id
  * @param $scopingEnabled
  * @param $marcRecord
  * @return mixed
  */
 public function getItemsFast($id, $scopingEnabled, $marcRecord = null)
 {
     if ($marcRecord == null) {
         $marcRecord = MarcLoader::loadMarcRecordByILSId($id);
         global $timer;
         $timer->logTime("Finished loading MARC Record for getItemsFast");
     }
     MillenniumDriver::loadLibraryLocationInformation();
     //Get the items Fields from the record
     /** @var File_MARC_Data_Field[] $itemFields */
     $itemFields = $marcRecord->getFields('989');
     global $timer;
     $timer->logTime("Finished loading item fields for {$id}, found " . count($itemFields));
     $items = array();
     $pType = $this->getPType();
     //$timer->logTime("Finished loading pType");
     global $configArray;
     $statusSubfield = $configArray['Reindex']['statusSubfield'];
     $iTypeSubfield = $configArray['Reindex']['iTypeSubfield'];
     $dueDateSubfield = $configArray['Reindex']['dueDateSubfield'];
     $lastCheckinDateSubfield = $configArray['Reindex']['lastCheckinDateSubfield'];
     foreach ($itemFields as $itemField) {
         //Ignore eContent items
         $eContentData = trim($itemField->getSubfield('w') != null ? $itemField->getSubfield('w')->getData() : '');
         if ($eContentData && strpos($eContentData, ':') > 0) {
             continue;
         }
         $locationCode = $itemField->getSubfield('d') != null ? trim($itemField->getSubfield('d')->getData()) : '';
         //Do a quick check of location code so we can remove this quickly when scoping is enabled
         if ($scopingEnabled && strlen(MillenniumDriver::$scopingLocationCode) > 0 && strpos($locationCode, MillenniumDriver::$scopingLocationCode) !== 0) {
             global $logger;
             $logger->log("Removed item because scoping is enabled and the location code {$locationCode} did not start with " . MillenniumDriver::$scopingLocationCode, PEAR_LOG_DEBUG);
             continue;
         }
         $iType = $itemField->getSubfield($iTypeSubfield) != null ? trim($itemField->getSubfield($iTypeSubfield)->getData()) : '';
         $holdable = $this->isItemHoldableToPatron($locationCode, $iType, $pType);
         $isLibraryItem = false;
         $locationLabel = '';
         foreach (MillenniumDriver::$libraryLocations as $tmpLocation) {
             if (strpos($locationCode, $tmpLocation) === 0) {
                 $isLibraryItem = true;
                 $locationLabel = MillenniumDriver::$libraryLocationLabels[$tmpLocation];
                 break;
             }
         }
         $timer->logTime("Finished checking if item is holdable");
         //Check to make sure the user has access to this item
         if ($holdable || $isLibraryItem) {
             $isLocalItem = false;
             if (MillenniumDriver::$homeLocationCode != null && strpos($locationCode, MillenniumDriver::$homeLocationCode) === 0) {
                 $isLocalItem = true;
                 $locationLabel = MillenniumDriver::$homeLocationLabel;
             }
             $status = trim($itemField->getSubfield($statusSubfield) != null ? trim($itemField->getSubfield($statusSubfield)->getData()) : '');
             $dueDate = $itemField->getSubfield($dueDateSubfield) != null ? trim($itemField->getSubfield($dueDateSubfield)->getData()) : null;
             $lastCheckinDate = $itemField->getSubfield($lastCheckinDateSubfield);
             if ($lastCheckinDate) {
                 // convert to timestamp for ease of display in template
                 $lastCheckinDate = trim($lastCheckinDate->getData());
                 $lastCheckinDate = DateTime::createFromFormat('m-d-Y G:i', $lastCheckinDate);
                 if ($lastCheckinDate) {
                     $lastCheckinDate = $lastCheckinDate->getTimestamp();
                 }
             }
             if (!$lastCheckinDate) {
                 $lastCheckinDate = null;
             }
             $available = in_array($status, array('-', 'o', 'd', 'w', ')', 'u')) && ($dueDate == null || strlen($dueDate) == 0);
             $inLibraryUseOnly = $status == 'o';
             $fullCallNumber = $itemField->getSubfield('s') != null ? $itemField->getSubfield('s')->getData() . ' ' : '';
             $fullCallNumber .= $itemField->getSubfield('a') != null ? $itemField->getSubfield('a')->getData() : '';
             $fullCallNumber .= $itemField->getSubfield('r') != null ? ' ' . $itemField->getSubfield('r')->getData() : '';
             $fullCallNumber .= $itemField->getSubfield('v') != null ? ' ' . $itemField->getSubfield('v')->getData() : '';
             $shelfLocation = mapValue('shelf_location', $locationCode);
             if (preg_match('/(.*?)\\sC\\d{3}\\w{0,2}$/', $shelfLocation, $locationParts)) {
                 $shelfLocation = $locationParts[1];
             }
             $item = array('location' => $locationCode, 'callnumber' => $fullCallNumber, 'availability' => $available, 'holdable' => $holdable, 'inLibraryUseOnly' => $inLibraryUseOnly, 'isLocalItem' => $isLocalItem, 'isLibraryItem' => $isLibraryItem, 'locationLabel' => $locationLabel, 'shelfLocation' => $shelfLocation, 'status' => $status, 'dueDate' => $dueDate, 'iType' => $iType, 'lastCheckinDate' => $lastCheckinDate);
             $items[] = $item;
         }
         //$timer->logTime("Finished processing item");
     }
     global $timer;
     $timer->logTime("Finished load items fast for Millennium record {$id} there were " . count($itemFields) . " item fields originally, filtered to " . count($items));
     return $items;
 }
 /**
  * Load status (holdings) for a record and filter them based on the logged in user information.
  *
  * Format of return array is:
  * key = {section#}{location}-### where ### is the holding iteration
  *
  * value = array (
  *  id = The id of the bib
  *  number = The position of the holding within the original list of holdings
  *  section = A description of the section
  *  sectionId = a numeric id of the section for sorting
  *  type = holding
  *  status
  *  statusfull
  *  availability
  *  holdable
  *  nonHoldableReason
  *  reserve
  *  holdQueueLength
  *  duedate
  *  location
  *  libraryDisplayName
  *  locationLink
  *  callnumber
  *  link = array
  *  linkText
  *  isDownload
  * )
  *
  * Includes both physical titles as well as titles on order
  *
  * @param string            $id     the id of the record
  * @return array A list of holdings for the record
  */
 public function getStatus($id)
 {
     if (array_key_exists($id, MillenniumStatusLoader::$loadedStatus)) {
         return MillenniumStatusLoader::$loadedStatus[$id];
     }
     global $library;
     global $user;
     global $timer;
     global $logger;
     global $configArray;
     $pType = $this->driver->getPType();
     $scope = $this->driver->getMillenniumScope();
     if (!$configArray['Catalog']['offline']) {
         //Get information about holdings, order information, and issue information
         $millenniumInfo = $this->driver->getMillenniumRecordInfo($id);
         //Get the number of holds
         if ($millenniumInfo->framesetInfo) {
             if (preg_match('/(\\d+) hold(s?) on .*? of \\d+ (copies|copy)/', $millenniumInfo->framesetInfo, $matches)) {
                 $holdQueueLength = $matches[1];
             } else {
                 $holdQueueLength = 0;
             }
         }
         // Load Record Page
         $r = substr($millenniumInfo->holdingsInfo, stripos($millenniumInfo->holdingsInfo, 'bibItems'));
         $r = substr($r, strpos($r, ">") + 1);
         $r = substr($r, 0, stripos($r, "</table"));
         $rows = preg_split("/<tr([^>]*)>/", $r);
     } else {
         $rows = array();
         $millenniumInfo = null;
     }
     //Load item information from marc record
     $matchItemsWithMarcItems = $configArray['Catalog']['matchItemsWithMarcItems'];
     if ($matchItemsWithMarcItems) {
         // Load the full marc record so we can get the iType for each record.
         $marcRecord = MarcLoader::loadMarcRecordByILSId($id);
         $marcItemField = isset($configArray['Reindex']['itemTag']) ? $configArray['Reindex']['itemTag'] : '989';
         $itemFields = $marcRecord->getFields($marcItemField);
         $marcItemData = array();
         //TODO: Don't hardcode item subfields
         $statusSubfield = $configArray['Reindex']['statusSubfield'];
         $dueDateSubfield = $configArray['Reindex']['dueDateSubfield'];
         $locationSubfield = $configArray['Reindex']['locationSubfield'];
         $iTypeSubfield = $configArray['Reindex']['iTypeSubfield'];
         $callNumberPrestampSubfield = $configArray['Reindex']['callNumberPrestampSubfield'];
         $callNumberSubfield = $configArray['Reindex']['callNumberSubfield'];
         $callNumberCutterSubfield = $configArray['Reindex']['callNumberCutterSubfield'];
         $callNumberPoststampSubfield = $configArray['Reindex']['callNumberPoststampSubfield'];
         $volumeSubfield = $configArray['Reindex']['volumeSubfield'];
         $lastCheckinDateSubfield = $configArray['Reindex']['lastCheckinDateSubfield'];
         // added plb 3-24-2015
         foreach ($itemFields as $itemField) {
             /** @var $itemField File_MARC_Data_Field */
             $fullCallNumber = $itemField->getSubfield($callNumberPrestampSubfield) != null ? $itemField->getSubfield($callNumberPrestampSubfield)->getData() . ' ' : '';
             $fullCallNumber .= $itemField->getSubfield($callNumberSubfield) != null ? $itemField->getSubfield($callNumberSubfield)->getData() : '';
             $fullCallNumber .= $itemField->getSubfield($callNumberCutterSubfield) != null ? ' ' . $itemField->getSubfield($callNumberCutterSubfield)->getData() : '';
             $fullCallNumber .= $itemField->getSubfield($callNumberPoststampSubfield) != null ? ' ' . $itemField->getSubfield($callNumberPoststampSubfield)->getData() : '';
             $fullCallNumber .= $itemField->getSubfield($volumeSubfield) != null ? ' ' . $itemField->getSubfield($volumeSubfield)->getData() : '';
             $fullCallNumber = str_replace('  ', ' ', $fullCallNumber);
             $itemData['callnumber'] = $fullCallNumber;
             $itemData['location'] = $itemField->getSubfield($locationSubfield) != null ? trim($itemField->getSubfield($locationSubfield)->getData()) : '?????';
             $itemData['iType'] = $itemField->getSubfield($iTypeSubfield) != null ? $itemField->getSubfield($iTypeSubfield)->getData() : '-1';
             $itemData['matched'] = false;
             $itemData['status'] = $itemField->getSubfield($statusSubfield) != null ? $itemField->getSubfield($statusSubfield)->getData() : '-';
             $itemData['dueDate'] = $itemField->getSubfield($dueDateSubfield) != null ? trim($itemField->getSubfield($dueDateSubfield)->getData()) : null;
             $lastCheckinDate = $itemField->getSubfield($lastCheckinDateSubfield);
             // added plb 3-24-2015
             if ($lastCheckinDate) {
                 // convert to timestamp for ease of display in template
                 $lastCheckinDate = trim($lastCheckinDate->getData());
                 $lastCheckinDate = DateTime::createFromFormat('m-d-Y G:i', $lastCheckinDate);
                 if ($lastCheckinDate) {
                     $lastCheckinDate = $lastCheckinDate->getTimestamp();
                 }
             }
             $itemData['lastCheckinDate'] = $lastCheckinDate ? $lastCheckinDate : null;
             $marcItemData[] = $itemData;
         }
     } else {
         $marcItemData = array();
         $marcRecord = null;
     }
     if (!$configArray['Catalog']['offline']) {
         //Process each row in the callnumber table.
         $ret = $this->parseHoldingRows($id, $rows);
         if (count($ret) == 0) {
             //Also check the frameset for links
             if (preg_match('/<div class="bibDisplayUrls">.*?<table.*?>(.*?)<\\/table>.*?<\\/div>/si', $millenniumInfo->framesetInfo, $displayUrlInfo)) {
                 $linksTable = $displayUrlInfo[1];
                 preg_match_all('/<td.*?>.*?<a href="(.*?)".*?>(.*?)<\\/a>.*?<\\/td>/si', $linksTable, $linkData, PREG_SET_ORDER);
                 for ($i = 0; $i < count($linkData); $i++) {
                     $linkText = $linkData[$i][2];
                     if ($linkText != 'Latest Received') {
                         $newHolding = array('type' => 'holding', 'link' => array(), 'status' => 'Online', 'location' => 'Online');
                         $newHolding['link'][] = array('link' => $linkData[$i][1], 'linkText' => $linkText, 'isDownload' => true);
                         $ret[] = $newHolding;
                     }
                 }
             }
         }
         $timer->logTime('processed all holdings rows');
     } else {
         $ret = null;
     }
     global $locationSingleton;
     /** @var $locationSingleton Location */
     $physicalLocation = $locationSingleton->getPhysicalLocation();
     if ($physicalLocation != null) {
         $physicalBranch = $physicalLocation->holdingBranchLabel;
     } else {
         $physicalBranch = '';
     }
     $homeBranch = '';
     $homeBranchId = 0;
     $nearbyBranch1 = '';
     $nearbyBranch1Id = 0;
     $nearbyBranch2 = '';
     $nearbyBranch2Id = 0;
     //Set location information based on the user login.  This will override information based
     if (isset($user) && $user != false) {
         $homeBranchId = $user->homeLocationId;
         $nearbyBranch1Id = $user->myLocation1Id;
         $nearbyBranch2Id = $user->myLocation2Id;
     } else {
         //Check to see if the cookie for home location is set.
         if (isset($_COOKIE['home_location']) && is_numeric($_COOKIE['home_location'])) {
             $cookieLocation = new Location();
             $locationId = $_COOKIE['home_location'];
             $cookieLocation->whereAdd("locationId = '{$locationId}'");
             $cookieLocation->find();
             if ($cookieLocation->N == 1) {
                 $cookieLocation->fetch();
                 $homeBranchId = $cookieLocation->locationId;
                 $nearbyBranch1Id = $cookieLocation->nearbyLocation1;
                 $nearbyBranch2Id = $cookieLocation->nearbyLocation2;
             }
         }
     }
     //Load the holding label for the user's home location.
     $userLocation = new Location();
     $userLocation->whereAdd("locationId = '{$homeBranchId}'");
     $userLocation->find();
     if ($userLocation->N == 1) {
         $userLocation->fetch();
         $homeBranch = $userLocation->holdingBranchLabel;
     }
     //Load nearby branch 1
     $nearbyLocation1 = new Location();
     $nearbyLocation1->whereAdd("locationId = '{$nearbyBranch1Id}'");
     $nearbyLocation1->find();
     if ($nearbyLocation1->N == 1) {
         $nearbyLocation1->fetch();
         $nearbyBranch1 = $nearbyLocation1->holdingBranchLabel;
     }
     //Load nearby branch 2
     $nearbyLocation2 = new Location();
     $nearbyLocation2->whereAdd();
     $nearbyLocation2->whereAdd("locationId = '{$nearbyBranch2Id}'");
     $nearbyLocation2->find();
     if ($nearbyLocation2->N == 1) {
         $nearbyLocation2->fetch();
         $nearbyBranch2 = $nearbyLocation2->holdingBranchLabel;
     }
     $sorted_array = array();
     //Get a list of the display names for all locations based on holding label.
     $locationLabels = array();
     $location = new Location();
     $location->find();
     $libraryLocationLabels = array();
     $locationCodes = array();
     $suppressedLocationCodes = array();
     while ($location->fetch()) {
         if (strlen($location->holdingBranchLabel) > 0 && $location->holdingBranchLabel != '???') {
             if ($library && $library->libraryId == $location->libraryId) {
                 $cleanLabel = str_replace('/', '\\/', $location->holdingBranchLabel);
                 $libraryLocationLabels[] = str_replace('.', '\\.', $cleanLabel);
             }
             $locationLabels[$location->holdingBranchLabel] = $location->displayName;
             $locationCodes[$location->code] = $location->holdingBranchLabel;
             if ($location->suppressHoldings == 1) {
                 $suppressedLocationCodes[$location->code] = $location->code;
             }
         }
     }
     if (count($libraryLocationLabels) > 0) {
         $libraryLocationLabels = '/^(' . join('|', $libraryLocationLabels) . ').*/i';
     } else {
         $libraryLocationLabels = '';
     }
     //Get the current Ptype for later usage.
     $timer->logTime('setup for additional holdings processing.');
     //Now that we have the holdings, we need to filter and sort them according to scoping rules.
     if (!$configArray['Catalog']['offline']) {
         $i = 0;
         foreach ($ret as $holdingKey => $holding) {
             $holding['type'] = 'holding';
             //Process holdings without call numbers - Need to show items without call numbers
             //because they may have links, etc.  Also show if there is a status.  Since some
             //In process items may not have a call number yet.
             if ((!isset($holding['callnumber']) || strlen($holding['callnumber']) == 0) && (!isset($holding['link']) || count($holding['link']) == 0) && !isset($holding['status'])) {
                 continue;
             }
             //Determine if the holding is available or not.
             //First check the status
             if (preg_match('/^(' . $this->driver->availableStatiRegex . ')$/', $holding['status'])) {
                 $holding['availability'] = 1;
             } else {
                 $holding['availability'] = 0;
             }
             if (preg_match('/^(' . $this->driver->holdableStatiRegex . ')$/', $holding['status'])) {
                 $holding['holdable'] = 1;
             } else {
                 $holding['holdable'] = 0;
                 $holding['nonHoldableReason'] = "This item is not currently available for Patron Holds";
             }
             if (!isset($holding['libraryDisplayName'])) {
                 $holding['libraryDisplayName'] = $holding['location'];
             }
             //Get the location id for this holding
             $holding['locationCode'] = '?????';
             foreach ($locationCodes as $locationCode => $holdingLabel) {
                 if (strlen($locationCode) > 0 && preg_match("~{$holdingLabel}~i", $holding['location'])) {
                     $holding['locationCode'] = $locationCode;
                     break;
                 }
             }
             if ($holding['locationCode'] == '?????') {
                 $logger->log("Did not find location code for " . $holding['location'] . " record {$id}", PEAR_LOG_DEBUG);
             }
             if (array_key_exists($holding['locationCode'], $suppressedLocationCodes)) {
                 $logger->log("Location " . $holding['locationCode'] . " is suppressed", PEAR_LOG_DEBUG);
                 continue;
             }
             //Now that we have the location code, try to match with the marc record
             $holding['iType'] = 0;
             if ($matchItemsWithMarcItems) {
                 foreach ($marcItemData as $itemKey => $itemData) {
                     if ($itemData['matched'] === false) {
                         // ensure not checked already
                         $locationMatched = strpos($itemData['location'], $holding['locationCode']) === 0;
                         $itemCallNumber = isset($itemData['callnumber']) ? $itemData['callnumber'] : '';
                         $holdingCallNumber = isset($holding['callnumber']) ? $holding['callnumber'] : '';
                         if (strlen($itemCallNumber) == 0 || strlen($holding['callnumber']) == 0) {
                             $callNumberMatched = strlen($itemCallNumber) == strlen($holdingCallNumber);
                         } else {
                             $callNumberMatched = strpos($itemCallNumber, $holdingCallNumber) >= 0;
                         }
                         if ($locationMatched && $callNumberMatched) {
                             $holding['iType'] = $itemData['iType'];
                             $holding['lastCheckinDate'] = $itemData['lastCheckinDate'];
                             // if the marc record matches up, include the last checkin date in the holding info.
                             //Get the more specific location code
                             if (strlen($holding['locationCode']) < strlen($itemData['location'])) {
                                 $holding['locationCode'] = $itemData['location'];
                             }
                             $itemData['matched'] = true;
                             $marcItemData[$itemKey] = $itemData;
                             // add matched sub-array element back to original array being looped over.
                             break;
                         }
                     }
                 }
                 //Check to see if this item can be held by the current patron.  Only important when
                 //we know what pType is in use and we are showing all items.
                 if ($scope == $this->driver->getDefaultScope() && $pType >= 0) {
                     //Never remove the title if it is owned by the current library (could be in library use only)
                     if (isset($library) && strlen($library->ilsCode) > 0 && strpos($holding['locationCode'], $library->ilsCode) === 0) {
                         //$logger->log("Cannot remove holding because it belongs to the active library", PEAR_LOG_DEBUG);
                     } else {
                         if (!$this->driver->isItemHoldableToPatron($holding['locationCode'], $holding['iType'], $pType)) {
                             //$logger->log("Removing item $holdingKey because it is not usable by the current patronType $pType, iType is {$holding['iType']}, location is {$holding['locationCode']}", PEAR_LOG_DEBUG);
                             //echo("Removing item $holdingKey because it is not usable by the current patronType $pType, iType is {$holding['iType']}, location is {$holding['locationCode']}");
                             unset($ret[$holdingKey]);
                             continue;
                         }
                     }
                 } else {
                     if ($pType >= 0 && $holding['holdable'] == 1) {
                         //We won't want to remove titles based on holdability, but we do want to check if it is holdable
                         if (!$this->driver->isItemHoldableToPatron($holding['locationCode'], $holding['iType'], $pType)) {
                             $holding['holdable'] = 0;
                         }
                     }
                 }
             }
             //Set the hold queue length
             $holding['holdQueueLength'] = isset($holdQueueLength) ? $holdQueueLength : null;
             //Add the holding to the sorted array to determine
             $paddedNumber = str_pad($i, 3, '0', STR_PAD_LEFT);
             $sortString = $holding['location'] . '-' . $paddedNumber;
             //$sortString = $holding['location'] . $holding['callnumber']. $i;
             if (strlen($physicalBranch) > 0 && stripos($holding['location'], $physicalBranch) !== false) {
                 //If the user is in a branch, those holdings come first.
                 $holding['section'] = 'In this library';
                 $holding['sectionId'] = 1;
                 $sorted_array['1' . $sortString] = $holding;
             } else {
                 if (strlen($homeBranch) > 0 && stripos($holding['location'], $homeBranch) !== false) {
                     //Next come the user's home branch if the user is logged in or has the home_branch cookie set.
                     $holding['section'] = 'Your library';
                     $holding['sectionId'] = 2;
                     $sorted_array['2' . $sortString] = $holding;
                 } else {
                     if (strlen($nearbyBranch1) > 0 && stripos($holding['location'], $nearbyBranch1) !== false) {
                         //Next come nearby locations for the user
                         $holding['section'] = 'Nearby Libraries';
                         $holding['sectionId'] = 3;
                         $sorted_array['3' . $sortString] = $holding;
                     } else {
                         if (strlen($nearbyBranch2) > 0 && stripos($holding['location'], $nearbyBranch2) !== false) {
                             //Next come nearby locations for the user
                             $holding['section'] = 'Nearby Libraries';
                             $holding['sectionId'] = 4;
                             $sorted_array['4' . $sortString] = $holding;
                         } else {
                             if (strlen($libraryLocationLabels) > 0 && preg_match($libraryLocationLabels, $holding['location'])) {
                                 //Next come any locations within the same system we are in.
                                 $holding['section'] = $library->displayName;
                                 $holding['sectionId'] = 5;
                                 $sorted_array['5' . $sortString] = $holding;
                             } else {
                                 //Finally, all other holdings are shown sorted alphabetically.
                                 $holding['section'] = 'Other Locations';
                                 $holding['sectionId'] = 6;
                                 $sorted_array['6' . $sortString] = $holding;
                             }
                         }
                     }
                 }
             }
             $i++;
         }
     } else {
         $i = 0;
         //Offline circ, process each item in the marc record
         foreach ($marcItemData as $marcData) {
             $i++;
             $holding = array();
             $holding['type'] = 'holding';
             $holding['locationCode'] = $marcData['location'];
             $holding['callnumber'] = $marcData['callnumber'];
             $holding['statusfull'] = $this->translateStatusCode($marcData['status'], $marcData['dueDate']);
             $holding['lastCheckinDate'] = $marcData['lastCheckinDate'];
             //Try to translate the location code at least to location
             $location = new Location();
             $location->whereAdd("LOCATE(code, '{$marcData['location']}') = 1");
             if ($location->find(true)) {
                 $holding['location'] = $location->displayName;
             } else {
                 $holding['location'] = $marcData['location'];
             }
             if (array_key_exists($holding['locationCode'], $suppressedLocationCodes)) {
                 $logger->log("Location " . $holding['locationCode'] . " is suppressed", PEAR_LOG_DEBUG);
                 continue;
             }
             $holding['iType'] = $marcData['iType'];
             if ($marcData['status'] == '-' && $marcData['dueDate'] == null) {
                 $holding['availability'] = 1;
             } else {
                 $holding['availability'] = 0;
             }
             //Check to see if this item can be held by the current patron.  Only important when
             //we know what pType is in use and we are showing all items.
             if ($scope == $this->driver->getDefaultScope() && $pType > 0) {
                 //Never remove the title if it is owned by the current library (could be in library use only)
                 if (isset($library) && strlen($library->ilsCode) > 0 && strpos($holding['locationCode'], $library->ilsCode) === 0) {
                     //$logger->log("Cannot remove holding because it belongs to the active library", PEAR_LOG_DEBUG);
                 } else {
                     if (!$this->driver->isItemHoldableToPatron($holding['locationCode'], $holding['iType'], $pType)) {
                         //$logger->log("Removing item because it is not usable by the current patronType $pType, iType is {$holding['iType']}, location is {$holding['locationCode']}", PEAR_LOG_DEBUG);
                         //echo("Removing item $holdingKey because it is not usable by the current patronType $pType, iType is {$holding['iType']}, location is {$holding['locationCode']}");
                         continue;
                     }
                 }
             }
             $paddedNumber = str_pad($i, 3, '0', STR_PAD_LEFT);
             $sortString = $holding['location'] . '-' . $paddedNumber;
             if (strlen($physicalBranch) > 0 && stripos($holding['location'], $physicalBranch) !== false) {
                 //If the user is in a branch, those holdings come first.
                 $holding['section'] = 'In this library';
                 $holding['sectionId'] = 1;
                 $sorted_array['1' . $sortString] = $holding;
             } else {
                 if (strlen($homeBranch) > 0 && stripos($holding['location'], $homeBranch) !== false) {
                     //Next come the user's home branch if the user is logged in or has the home_branch cookie set.
                     $holding['section'] = 'Your library';
                     $holding['sectionId'] = 2;
                     $sorted_array['2' . $sortString] = $holding;
                 } else {
                     if (strlen($nearbyBranch1) > 0 && stripos($holding['location'], $nearbyBranch1) !== false) {
                         //Next come nearby locations for the user
                         $holding['section'] = 'Nearby Libraries';
                         $holding['sectionId'] = 3;
                         $sorted_array['3' . $sortString] = $holding;
                     } else {
                         if (strlen($nearbyBranch2) > 0 && stripos($holding['location'], $nearbyBranch2) !== false) {
                             //Next come nearby locations for the user
                             $holding['section'] = 'Nearby Libraries';
                             $holding['sectionId'] = 4;
                             $sorted_array['4' . $sortString] = $holding;
                         } else {
                             if (strlen($libraryLocationLabels) > 0 && preg_match($libraryLocationLabels, $holding['location'])) {
                                 //Next come any locations within the same system we are in.
                                 $holding['section'] = $library->displayName;
                                 $holding['sectionId'] = 5;
                                 $sorted_array['5' . $sortString] = $holding;
                             } else {
                                 //Finally, all other holdings are shown sorted alphabetically.
                                 $holding['section'] = 'Other Locations';
                                 $holding['sectionId'] = 6;
                                 $sorted_array['6' . $sortString] = $holding;
                             }
                         }
                     }
                 }
             }
         }
     }
     $timer->logTime('finished processing holdings');
     //Check to see if the title is holdable
     $holdable = $this->driver->isRecordHoldable($marcRecord);
     foreach ($sorted_array as $key => $holding) {
         //Do not override holdability based on status
         if (isset($holding['holdable']) && $holding['holdable'] == 1) {
             $holding['holdable'] = $holdable ? 1 : 0;
             $sorted_array[$key] = $holding;
         }
     }
     if (!$configArray['Catalog']['offline']) {
         //Load order records, these only show in the full page view, not the item display
         $orderMatches = array();
         if (preg_match_all('/<tr\\s+class="bibOrderEntry">.*?<td\\s*>(.*?)<\\/td>/s', $millenniumInfo->framesetInfo, $orderMatches)) {
             for ($i = 0; $i < count($orderMatches[1]); $i++) {
                 $location = trim($orderMatches[1][$i]);
                 $location = preg_replace('/\\sC\\d{3}\\w{0,2}[\\s\\.]/', '', $location);
                 //Remove courier code if any
                 $sorted_array['7' . $location . $i] = array('location' => $location, 'section' => 'On Order', 'sectionId' => 7, 'holdable' => 1);
             }
         }
         $timer->logTime('loaded order records');
     }
     ksort($sorted_array);
     //Check to see if we can remove the sections.
     //We can if all section keys are the same.
     $removeSection = true;
     $lastKeyIndex = '';
     foreach ($sorted_array as $key => $holding) {
         $currentKey = substr($key, 0, 1);
         if ($lastKeyIndex == '') {
             $lastKeyIndex = $currentKey;
         } else {
             if ($lastKeyIndex != $currentKey) {
                 $removeSection = false;
                 break;
             }
         }
     }
     foreach ($sorted_array as $key => $holding) {
         if ($removeSection == true) {
             $holding['section'] = '';
             $sorted_array[$key] = $holding;
         }
     }
     if (!$configArray['Catalog']['offline']) {
         $issueSummaries = $this->driver->getIssueSummaries($millenniumInfo);
     } else {
         $issueSummaries = null;
     }
     $timer->logTime('loaded issue summaries');
     if (!is_null($issueSummaries)) {
         krsort($sorted_array);
         //Group holdings under the issue issue summary that is related.
         foreach ($sorted_array as $key => $holding) {
             //Have issue summary = false
             $haveIssueSummary = false;
             $issueSummaryKey = null;
             foreach ($issueSummaries as $issueKey => $issueSummary) {
                 if ($issueSummary['location'] == $holding['location']) {
                     $haveIssueSummary = true;
                     $issueSummaryKey = $issueKey;
                     break;
                 }
             }
             if ($haveIssueSummary) {
                 $issueSummaries[$issueSummaryKey]['holdings'][strtolower($key)] = $holding;
             } else {
                 //Need to automatically add a summary so we don't lose data
                 $issueSummaries[$holding['location']] = array('location' => $holding['location'], 'type' => 'issue', 'holdings' => array(strtolower($key) => $holding));
             }
         }
         foreach ($issueSummaries as $key => $issueSummary) {
             if (isset($issueSummary['holdings']) && is_array($issueSummary['holdings'])) {
                 krsort($issueSummary['holdings']);
                 $issueSummaries[$key] = $issueSummary;
             }
         }
         ksort($issueSummaries);
         $status = $issueSummaries;
     } else {
         $status = $sorted_array;
     }
     MillenniumStatusLoader::$loadedStatus[$id] = $status;
     return $status;
 }
 /**
  * Place Item Hold
  *
  * This is responsible for both placing item level holds.
  *
  * @param   string  $recordId   The id of the bib record
  * @param   string  $itemId     The id of the item to hold
  * @param   string  $patronId   The id of the patron
  * @param   string  $comment    Any comment regarding the hold or recall
  * @param   string  $type       Whether to place a hold or recall
  * @param   string  $type       The date when the hold should be cancelled if any
  * @return  mixed               True if successful, false if unsuccessful
  *                              If an error occurs, return a PEAR_Error
  * @access  public
  */
 public function placeItemHold($recordId, $itemId, $patronId, $comment, $type)
 {
     $patronDump = $this->driver->_getPatronDump($this->driver->_getBarcode());
     $bib1 = $recordId;
     if (substr($bib1, 0, 1) != '.') {
         $bib1 = '.' . $bib1;
     }
     $bib = substr(str_replace('.b', 'b', $bib1), 0, -1);
     if (strlen($bib) == 0) {
         return array('result' => false, 'message' => 'A valid record id was not provided. Please try again.');
     }
     //Get the title of the book.
     global $configArray;
     $class = $configArray['Index']['engine'];
     $url = $configArray['Index']['url'];
     $this->driver->db = new $class($url);
     if ($configArray['System']['debugSolr']) {
         $this->driver->db->debug = true;
     }
     // Retrieve Full Marc Record
     if (!($record = $this->driver->db->getRecord($bib1))) {
         $title = null;
     } else {
         if (isset($record['title_full'][0])) {
             $title = $record['title_full'][0];
         } else {
             $title = $record['title'];
         }
     }
     //Cancel a hold
     if ($type == 'cancel' || $type == 'recall' || $type == 'update') {
         $result = $this->updateHold($recordId, $patronId, $type, $title);
         $result['title'] = $title;
         $result['bid'] = $bib1;
         return $result;
     } else {
         //User is logged in before they get here, always use the info from patrondump
         $username = $patronDump['PATRN_NAME'];
         if (isset($_REQUEST['canceldate']) && !is_null($_REQUEST['canceldate']) && $_REQUEST['canceldate'] != '') {
             $date = $_REQUEST['canceldate'];
         } else {
             //Default to a date 6 months (half a year) in the future.
             $sixMonthsFromNow = time() + 182.5 * 24 * 60 * 60;
             $date = date('m/d/Y', $sixMonthsFromNow);
         }
         if (isset($_POST['campus'])) {
             $campus = trim($_POST['campus']);
         } else {
             global $user;
             $campus = $user->homeLocationId;
         }
         if (is_numeric($campus)) {
             $location = new Location();
             $location->locationId = $campus;
             if ($location->find(true)) {
                 $campus = $location->code;
             }
         }
         list($Month, $Day, $Year) = explode("/", $date);
         //------------BEGIN CURL-----------------------------------------------------------------
         $fullName = $patronDump['PATRN_NAME'];
         $nameParts = explode(', ', $fullName);
         $lastName = $nameParts[0];
         if (isset($nameParts[1])) {
             $secondName = $nameParts[1];
             $secondNameParts = explode(' ', $secondName);
             $firstName = $secondNameParts[0];
             if (isset($secondNameParts[1])) {
                 $middleName = $secondNameParts[1];
             }
         }
         list($first, $last) = explode(' ', $username);
         $header = array();
         $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
         $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
         $header[] = "Cache-Control: max-age=0";
         $header[] = "Connection: keep-alive";
         $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
         $header[] = "Accept-Language: en-us,en;q=0.5";
         $id = $patronDump['RECORD_#'];
         $cookie = tempnam("/tmp", "CURLCOOKIE");
         $curl_connection = curl_init();
         curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 30);
         curl_setopt($curl_connection, CURLOPT_HTTPHEADER, $header);
         curl_setopt($curl_connection, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
         curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
         curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
         curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, true);
         curl_setopt($curl_connection, CURLOPT_UNRESTRICTED_AUTH, true);
         curl_setopt($curl_connection, CURLOPT_COOKIEJAR, $cookie);
         curl_setopt($curl_connection, CURLOPT_COOKIESESSION, true);
         curl_setopt($curl_connection, CURLOPT_FORBID_REUSE, false);
         curl_setopt($curl_connection, CURLOPT_HEADER, false);
         curl_setopt($curl_connection, CURLOPT_POST, true);
         if (isset($configArray['Catalog']['loginPriorToPlacingHolds']) && ($configArray['Catalog']['loginPriorToPlacingHolds'] = true)) {
             //User must be logged in as a separate step to placing holds
             $curl_url = $configArray['Catalog']['url'] . "/patroninfo";
             $post_data = $this->driver->_getLoginFormValues();
             $post_data['submit.x'] = "35";
             $post_data['submit.y'] = "21";
             $post_data['submit'] = "submit";
             curl_setopt($curl_connection, CURLOPT_REFERER, $curl_url);
             curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
             $post_items = array();
             foreach ($post_data as $key => $value) {
                 $post_items[] = $key . '=' . $value;
             }
             $post_string = implode('&', $post_items);
             curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
             curl_exec($curl_connection);
             $post_data = array();
         } else {
             $post_data = $this->driver->_getLoginFormValues();
         }
         $curl_url = $configArray['Catalog']['url'] . "/search/." . $bib . "/." . $bib . "/1,1,1,B/request~" . $bib;
         //echo "$curl_url";
         curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
         $post_data['needby_Month'] = $Month;
         $post_data['needby_Day'] = $Day;
         $post_data['needby_Year'] = $Year;
         $post_data['submit.x'] = "35";
         $post_data['submit.y'] = "21";
         $post_data['submit'] = "submit";
         $post_data['locx00'] = str_pad($campus, 5 - strlen($campus), '+');
         if (!is_null($itemId) && $itemId != -1) {
             $post_data['radio'] = $itemId;
         }
         $post_data['x'] = "48";
         $post_data['y'] = "15";
         $post_items = array();
         foreach ($post_data as $key => $value) {
             $post_items[] = $key . '=' . $value;
         }
         $post_string = implode('&', $post_items);
         curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
         $sresult = curl_exec($curl_connection);
         global $logger;
         $logger->log("Placing hold {$curl_url}?{$post_string}", PEAR_LOG_INFO);
         $sresult = preg_replace("/<!--([^(-->)]*)-->/", "", $sresult);
         curl_close($curl_connection);
         //Parse the response to get the status message
         $hold_result = $this->_getHoldResult($sresult);
         $hold_result['title'] = $title;
         $hold_result['bid'] = $bib1;
         global $analytics;
         if ($analytics) {
             if ($hold_result['result'] == true) {
                 $analytics->addEvent('ILS Integration', 'Successful Hold', $title);
             } else {
                 $analytics->addEvent('ILS Integration', 'Failed Hold', $hold_result['message'] . ' - ' . $title);
             }
         }
         return $hold_result;
     }
 }
 /**
  * Load status (holdings) for a record and filter them based on the logged in user information.
  *
  * @param string            $id     the id of the record
  * @return array A list of holdings for the record
  */
 public function getStatus($id)
 {
     global $library;
     global $user;
     global $timer;
     global $logger;
     //Get information about holdings, order information, and issue information
     $millenniumInfo = $this->driver->getMillenniumRecordInfo($id);
     //Get the number of holds
     if ($millenniumInfo->framesetInfo) {
         if (preg_match('/(\\d+) hold(s?) on .*? of \\d+ (copies|copy)/', $millenniumInfo->framesetInfo, $matches)) {
             $holdQueueLength = $matches[1];
         } else {
             $holdQueueLength = 0;
         }
     }
     // Load Record Page
     $r = substr($millenniumInfo->holdingsInfo, stripos($millenniumInfo->holdingsInfo, 'bibItems'));
     $r = substr($r, strpos($r, ">") + 1);
     $r = substr($r, 0, stripos($r, "</table"));
     $rows = preg_split("/<tr([^>]*)>/", $r);
     // Load the full marc record so we can get the iType for each record.
     $marcRecord = MarcLoader::loadMarcRecordByILSId($id);
     $itemFields = $marcRecord->getFields("989");
     $marcItemData = array();
     $pType = $this->driver->getPType();
     $scope = $this->driver->getMillenniumScope();
     //Load item information from marc record
     foreach ($itemFields as $itemField) {
         /** @var $itemField File_MARC_Data_Field */
         $fullCallNumber = $itemField->getSubfield('s') != null ? $itemField->getSubfield('s')->getData() . ' ' : '';
         $fullCallNumber .= $itemField->getSubfield('a') != null ? $itemField->getSubfield('a')->getData() : '';
         $fullCallNumber .= $itemField->getSubfield('r') != null ? ' ' . $itemField->getSubfield('r')->getData() : '';
         $itemData['callnumber'] = $fullCallNumber;
         $itemData['location'] = $itemField->getSubfield('d') != null ? $itemField->getSubfield('d')->getData() : ($itemField->getSubfield('p') != null ? $itemField->getSubfield('p')->getData() : '?????');
         $itemData['iType'] = $itemField->getSubfield('j') != null ? $itemField->getSubfield('j')->getData() : '0';
         $itemData['matched'] = false;
         $marcItemData[] = $itemData;
     }
     //Process each row in the callnumber table.
     $ret = $this->parseHoldingRows($id, $rows);
     $timer->logTime('processed all holdings rows');
     global $locationSingleton;
     /** @var $locationSingleton Location */
     $physicalLocation = $locationSingleton->getPhysicalLocation();
     if ($physicalLocation != null) {
         $physicalBranch = $physicalLocation->holdingBranchLabel;
     } else {
         $physicalBranch = '';
     }
     $homeBranch = '';
     $homeBranchId = 0;
     $nearbyBranch1 = '';
     $nearbyBranch1Id = 0;
     $nearbyBranch2 = '';
     $nearbyBranch2Id = 0;
     //Set location information based on the user login.  This will override information based
     if (isset($user) && $user != false) {
         $homeBranchId = $user->homeLocationId;
         $nearbyBranch1Id = $user->myLocation1Id;
         $nearbyBranch2Id = $user->myLocation2Id;
     } else {
         //Check to see if the cookie for home location is set.
         if (isset($_COOKIE['home_location']) && is_numeric($_COOKIE['home_location'])) {
             $cookieLocation = new Location();
             $locationId = $_COOKIE['home_location'];
             $cookieLocation->whereAdd("locationId = '{$locationId}'");
             $cookieLocation->find();
             if ($cookieLocation->N == 1) {
                 $cookieLocation->fetch();
                 $homeBranchId = $cookieLocation->locationId;
                 $nearbyBranch1Id = $cookieLocation->nearbyLocation1;
                 $nearbyBranch2Id = $cookieLocation->nearbyLocation2;
             }
         }
     }
     //Load the holding label for the user's home location.
     $userLocation = new Location();
     $userLocation->whereAdd("locationId = '{$homeBranchId}'");
     $userLocation->find();
     if ($userLocation->N == 1) {
         $userLocation->fetch();
         $homeBranch = $userLocation->holdingBranchLabel;
     }
     //Load nearby branch 1
     $nearbyLocation1 = new Location();
     $nearbyLocation1->whereAdd("locationId = '{$nearbyBranch1Id}'");
     $nearbyLocation1->find();
     if ($nearbyLocation1->N == 1) {
         $nearbyLocation1->fetch();
         $nearbyBranch1 = $nearbyLocation1->holdingBranchLabel;
     }
     //Load nearby branch 2
     $nearbyLocation2 = new Location();
     $nearbyLocation2->whereAdd();
     $nearbyLocation2->whereAdd("locationId = '{$nearbyBranch2Id}'");
     $nearbyLocation2->find();
     if ($nearbyLocation2->N == 1) {
         $nearbyLocation2->fetch();
         $nearbyBranch2 = $nearbyLocation2->holdingBranchLabel;
     }
     $sorted_array = array();
     //Get a list of the display names for all locations based on holding label.
     $locationLabels = array();
     $location = new Location();
     $location->find();
     $libraryLocationLabels = array();
     $locationCodes = array();
     $suppressedLocationCodes = array();
     while ($location->fetch()) {
         if (strlen($location->holdingBranchLabel) > 0 && $location->holdingBranchLabel != '???') {
             if ($library && $library->libraryId == $location->libraryId) {
                 $cleanLabel = str_replace('/', '\\/', $location->holdingBranchLabel);
                 $libraryLocationLabels[] = str_replace('.', '\\.', $cleanLabel);
             }
             $locationLabels[$location->holdingBranchLabel] = $location->displayName;
             $locationCodes[$location->code] = $location->holdingBranchLabel;
             if ($location->suppressHoldings == 1) {
                 $suppressedLocationCodes[$location->code] = $location->code;
             }
         }
     }
     if (count($libraryLocationLabels) > 0) {
         $libraryLocationLabels = '/^(' . join('|', $libraryLocationLabels) . ').*/i';
     } else {
         $libraryLocationLabels = '';
     }
     //Get the current Ptype for later usage.
     $timer->logTime('setup for additional holdings processing.');
     //Now that we have the holdings, we need to filter and sort them according to scoping rules.
     $i = 0;
     foreach ($ret as $holdingKey => $holding) {
         $holding['type'] = 'holding';
         //Process holdings without call numbers - Need to show items without call numbers
         //because they may have links, etc.  Also show if there is a status.  Since some
         //In process items may not have a call number yet.
         if ((!isset($holding['callnumber']) || strlen($holding['callnumber']) == 0) && (!isset($holding['link']) || count($holding['link']) == 0) && !isset($holding['status'])) {
             continue;
         }
         //Determine if the holding is available or not.
         //First check the status
         if (preg_match('/^(' . $this->driver->availableStatiRegex . ')$/', $holding['status'])) {
             $holding['availability'] = 1;
         } else {
             $holding['availability'] = 0;
         }
         if (preg_match('/^(' . $this->driver->holdableStatiRegex . ')$/', $holding['status'])) {
             $holding['holdable'] = 1;
         } else {
             $holding['holdable'] = 0;
             $holding['nonHoldableReason'] = "This item is not currently available for Patron Holds";
         }
         if (!isset($holding['libraryDisplayName'])) {
             $holding['libraryDisplayName'] = $holding['location'];
         }
         //Get the location id for this holding
         $holding['locationCode'] = '?????';
         foreach ($locationCodes as $locationCode => $holdingLabel) {
             if (strlen($locationCode) > 0 && preg_match("~{$holdingLabel}~i", $holding['location'])) {
                 $holding['locationCode'] = $locationCode;
             }
         }
         if ($holding['locationCode'] == '?????') {
             $logger->log("Did not find location code for " . $holding['location'], PEAR_LOG_DEBUG);
         }
         if (array_key_exists($holding['locationCode'], $suppressedLocationCodes)) {
             $logger->log("Location " . $holding['locationCode'] . " is suppressed", PEAR_LOG_DEBUG);
             continue;
         }
         //Now that we have the location code, try to match with the marc record
         $holding['iType'] = 0;
         foreach ($marcItemData as $itemData) {
             if (!$itemData['matched']) {
                 $locationMatched = strpos($itemData['location'], $holding['locationCode']) === 0;
                 if (strlen($itemData['callnumber']) == 0 || strlen($holding['callnumber']) == 0) {
                     $callNumberMatched = strlen($holding['callnumber']) == strlen($holding['callnumber']);
                 } else {
                     $callNumberMatched = strpos($itemData['callnumber'], $holding['callnumber']) >= 0;
                 }
                 if ($locationMatched && $callNumberMatched) {
                     $holding['iType'] = $itemData['iType'];
                     $itemData['matched'] = true;
                 }
             }
         }
         //Check to see if this item can be held by the current patron.  Only important when
         //we know what pType is in use and we are showing all items.
         if ($scope == 93 && $pType > 0) {
             //Never remove the title if it is owned by the current library (could be in library use only)
             if (isset($library) && strlen($library->ilsCode) > 0 && strpos($holding['locationCode'], $library->ilsCode) === 0) {
                 $logger->log("Cannot remove holding because it belongs to the active library", PEAR_LOG_DEBUG);
             } else {
                 if (!$this->driver->isItemHoldableToPatron($holding['locationCode'], $holding['iType'], $pType)) {
                     $logger->log("Removing item {$holdingKey} because it is not usable by the current patronType {$pType}, iType is {$holding['iType']}, location is {$holding['locationCode']}", PEAR_LOG_DEBUG);
                     //echo("Removing item $holdingKey because it is not usable by the current patronType $pType, iType is {$holding['iType']}, location is {$holding['locationCode']}");
                     unset($ret[$holdingKey]);
                     continue;
                 }
             }
         }
         //Set the hold queue length
         $holding['holdQueueLength'] = isset($holdQueueLength) ? $holdQueueLength : null;
         //Add the holding to the sorted array to determine
         $sortString = $holding['location'] . '-' . $i;
         //$sortString = $holding['location'] . $holding['callnumber']. $i;
         if (strlen($physicalBranch) > 0 && stripos($holding['location'], $physicalBranch) !== false) {
             //If the user is in a branch, those holdings come first.
             $holding['section'] = 'In this library';
             $holding['sectionId'] = 1;
             $sorted_array['1' . $sortString] = $holding;
         } else {
             if (strlen($homeBranch) > 0 && stripos($holding['location'], $homeBranch) !== false) {
                 //Next come the user's home branch if the user is logged in or has the home_branch cookie set.
                 $holding['section'] = 'Your library';
                 $holding['sectionId'] = 2;
                 $sorted_array['2' . $sortString] = $holding;
             } else {
                 if (strlen($nearbyBranch1) > 0 && stripos($holding['location'], $nearbyBranch1) !== false) {
                     //Next come nearby locations for the user
                     $holding['section'] = 'Nearby Libraries';
                     $holding['sectionId'] = 3;
                     $sorted_array['3' . $sortString] = $holding;
                 } else {
                     if (strlen($nearbyBranch2) > 0 && stripos($holding['location'], $nearbyBranch2) !== false) {
                         //Next come nearby locations for the user
                         $holding['section'] = 'Nearby Libraries';
                         $holding['sectionId'] = 4;
                         $sorted_array['4' . $sortString] = $holding;
                     } else {
                         if (strlen($libraryLocationLabels) > 0 && preg_match($libraryLocationLabels, $holding['location'])) {
                             //Next come any locations within the same system we are in.
                             $holding['section'] = $library->displayName;
                             $holding['sectionId'] = 5;
                             $sorted_array['5' . $sortString] = $holding;
                         } else {
                             //Finally, all other holdings are shown sorted alphabetically.
                             $holding['section'] = 'Other Locations';
                             $holding['sectionId'] = 6;
                             $sorted_array['6' . $sortString] = $holding;
                         }
                     }
                 }
             }
         }
         $i++;
     }
     $timer->logTime('finished processing holdings');
     //Check to see if the title is holdable
     $holdable = $this->driver->isRecordHoldable($marcRecord);
     foreach ($sorted_array as $key => $holding) {
         $holding['holdable'] = $holdable ? 1 : 0;
         $sorted_array[$key] = $holding;
     }
     //Load order records, these only show in the full page view, not the item display
     $orderMatches = array();
     if (preg_match_all('/<tr\\s+class="bibOrderEntry">.*?<td\\s*>(.*?)<\\/td>/s', $millenniumInfo->framesetInfo, $orderMatches)) {
         for ($i = 0; $i < count($orderMatches[1]); $i++) {
             $location = trim($orderMatches[1][$i]);
             $location = preg_replace('/\\sC\\d{3}[\\s\\.]/', '', $location);
             //Remove courier code if any
             $sorted_array['7' . $location . $i] = array('location' => $location, 'section' => 'On Order', 'sectionId' => 7, 'holdable' => 1);
         }
     }
     $timer->logTime('loaded order records');
     ksort($sorted_array);
     //Check to see if we can remove the sections.
     //We can if all section keys are the same.
     $removeSection = true;
     $lastKeyIndex = '';
     foreach ($sorted_array as $key => $holding) {
         $currentKey = substr($key, 0, 1);
         if ($lastKeyIndex == '') {
             $lastKeyIndex = $currentKey;
         } else {
             if ($lastKeyIndex != $currentKey) {
                 $removeSection = false;
                 break;
             }
         }
     }
     foreach ($sorted_array as $key => $holding) {
         if ($removeSection == true) {
             $holding['section'] = '';
             $sorted_array[$key] = $holding;
         }
     }
     $issueSummaries = $this->driver->getIssueSummaries($millenniumInfo);
     $timer->logTime('loaded issue summaries');
     if (!is_null($issueSummaries)) {
         krsort($sorted_array);
         //Group holdings under the issue issue summary that is related.
         foreach ($sorted_array as $key => $holding) {
             //Have issue summary = false
             $haveIssueSummary = false;
             $issueSummaryKey = null;
             foreach ($issueSummaries as $issueKey => $issueSummary) {
                 if ($issueSummary['location'] == $holding['location']) {
                     $haveIssueSummary = true;
                     $issueSummaryKey = $issueKey;
                     break;
                 }
             }
             if ($haveIssueSummary) {
                 $issueSummaries[$issueSummaryKey]['holdings'][strtolower($key)] = $holding;
             } else {
                 //Need to automatically add a summary so we don't lose data
                 $issueSummaries[$holding['location']] = array('location' => $holding['location'], 'type' => 'issue', 'holdings' => array(strtolower($key) => $holding));
             }
         }
         foreach ($issueSummaries as $key => $issueSummary) {
             if (isset($issueSummary['holdings']) && is_array($issueSummary['holdings'])) {
                 krsort($issueSummary['holdings']);
                 $issueSummaries[$key] = $issueSummary;
             }
         }
         ksort($issueSummaries);
         return $issueSummaries;
     } else {
         return $sorted_array;
     }
 }
 /**
  * Do an update or edit of reading history information.  Current actions are:
  * deleteMarked
  * deleteAll
  * exportList
  * optOut
  *
  * @param   string  $action         The action to perform
  * @param   array   $selectedTitles The titles to do the action on if applicable
  */
 function doReadingHistoryAction($action, $selectedTitles)
 {
     global $configArray;
     global $analytics;
     $patronDump = $this->driver->_getPatronDump($this->driver->_getBarcode());
     //Load the reading history page
     $scope = $this->driver->getDefaultScope();
     $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/readinghistory";
     $cookie = tempnam("/tmp", "CURLCOOKIE");
     $curl_connection = curl_init($curl_url);
     curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 30);
     curl_setopt($curl_connection, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
     curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
     curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
     curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, 1);
     curl_setopt($curl_connection, CURLOPT_UNRESTRICTED_AUTH, true);
     curl_setopt($curl_connection, CURLOPT_COOKIEJAR, $cookie);
     curl_setopt($curl_connection, CURLOPT_COOKIESESSION, true);
     curl_setopt($curl_connection, CURLOPT_POST, true);
     $post_data = $this->driver->_getLoginFormValues();
     $post_items = array();
     foreach ($post_data as $key => $value) {
         $post_items[] = $key . '=' . urlencode($value);
     }
     $post_string = implode('&', $post_items);
     curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
     $sResult = curl_exec($curl_connection);
     if ($action == 'deleteMarked') {
         //Load patron page readinghistory/rsh with selected titles marked
         if (!isset($selectedTitles) || count($selectedTitles) == 0) {
             return;
         }
         $titles = array();
         foreach ($selectedTitles as $titleId) {
             $titles[] = $titleId . '=1';
         }
         $title_string = implode('&', $titles);
         //Issue a get request to delete the item from the reading history.
         //Note: Millennium really does issue a malformed url, and it is required
         //to make the history delete properly.
         $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/readinghistory/rsh&" . $title_string;
         curl_setopt($curl_connection, CURLOPT_HTTPGET, true);
         curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
         $sResult = curl_exec($curl_connection);
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Delete Marked Reading History Titles');
         }
     } elseif ($action == 'deleteAll') {
         //load patron page readinghistory/rah
         $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/readinghistory/rah";
         curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
         curl_setopt($curl_connection, CURLOPT_HTTPGET, true);
         curl_exec($curl_connection);
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Delete All Reading History Titles');
         }
     } elseif ($action == 'exportList') {
         //Leave this unimplemented for now.
     } elseif ($action == 'optOut') {
         //load patron page readinghistory/OptOut
         $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/readinghistory/OptOut";
         curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
         curl_setopt($curl_connection, CURLOPT_HTTPGET, true);
         curl_exec($curl_connection);
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Opt Out of Reading History');
         }
     } elseif ($action == 'optIn') {
         //load patron page readinghistory/OptIn
         $curl_url = $configArray['Catalog']['url'] . "/patroninfo~S{$scope}/" . $patronDump['RECORD_#'] . "/readinghistory/OptIn";
         curl_setopt($curl_connection, CURLOPT_URL, $curl_url);
         curl_setopt($curl_connection, CURLOPT_HTTPGET, true);
         curl_exec($curl_connection);
         if ($analytics) {
             $analytics->addEvent('ILS Integration', 'Opt in to Reading History');
         }
     }
     curl_close($curl_connection);
     unlink($cookie);
 }