/** * 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); }
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(" ", " ", $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; }
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(); }
/** * 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); }