/**
  * 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;
 }
 public function getMyTransactions($page = 1, $recordsPerPage = -1, $sortOption = 'dueDate')
 {
     global $timer;
     $patronDump = $this->driver->_getPatronDump($this->driver->_getBarcode());
     $timer->logTime("Ready to load checked out titles from Millennium");
     //Load the information from millennium using CURL
     $sResult = $this->driver->_fetchPatronInfoPage($patronDump, 'items');
     $timer->logTime("Loaded checked out titles from Millennium");
     $sResult = preg_replace("/<[^<]+?>\\W<[^<]+?>\\W\\d* ITEM.? CHECKED OUT<[^<]+?>\\W<[^<]+?>/", "", $sResult);
     $s = substr($sResult, stripos($sResult, 'patFunc'));
     $s = substr($s, strpos($s, ">") + 1);
     $s = substr($s, 0, stripos($s, "</table"));
     $s = preg_replace("/<br \\/>/", "", $s);
     $sRows = preg_split("/<tr([^>]*)>/", $s);
     $sCount = 0;
     $sKeys = array_pad(array(), 10, "");
     $checkedOutTitles = array();
     //Get patron's location to determine if renewals are allowed.
     global $locationSingleton;
     /** @var Location $patronLocation */
     $patronLocation = $locationSingleton->getUserHomeLocation();
     if (isset($patronLocation)) {
         $patronPType = $this->driver->getPType();
         $patronCanRenew = false;
         if ($patronLocation->ptypesToAllowRenewals == '*') {
             $patronCanRenew = true;
         } else {
             if (preg_match("/^({$patronLocation->ptypesToAllowRenewals})\$/", $patronPType)) {
                 $patronCanRenew = true;
             }
         }
     } else {
         $patronCanRenew = true;
     }
     $timer->logTime("Determined if patron can renew");
     foreach ($sRows as $srow) {
         $scols = preg_split("/<t(h|d)([^>]*)>/", $srow);
         $curTitle = 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(substr($scols[$i], 0, stripos($scols[$i], "</t"))));
             //print_r($scols[$i]);
             if ($sCount == 1) {
                 $sKeys[$i] = $scols[$i];
             } else {
                 if ($sCount > 1) {
                     if (stripos($sKeys[$i], "TITLE") > -1) {
                         if (preg_match('/.*?<a href=\\"\\/record=(.*?)(?:~S\\d{1,2})\\">(.*?)<\\/a>.*/', $scols[$i], $matches)) {
                             $shortId = $matches[1];
                             $bibid = '.' . $matches[1];
                             $title = $matches[2];
                         } else {
                             $title = $scols[$i];
                             $shortId = '';
                             $bibid = '';
                         }
                         $curTitle['shortId'] = $shortId;
                         $curTitle['id'] = $bibid;
                         $curTitle['title'] = $title;
                     }
                     if (stripos($sKeys[$i], "STATUS") > -1) {
                         // $sret[$scount-2]['duedate'] = strip_tags($scols[$i]);
                         $due = trim(str_replace("DUE", "", strip_tags($scols[$i])));
                         $renewCount = 0;
                         if (preg_match('/FINE\\(up to now\\) (\\$\\d+\\.\\d+)/i', $due, $matches)) {
                             $curTitle['fine'] = trim($matches[1]);
                         }
                         if (preg_match('/(.*)Renewed (\\d+) time(?:s)?/i', $due, $matches)) {
                             $due = trim($matches[1]);
                             $renewCount = $matches[2];
                         } else {
                             if (preg_match('/(.*)\\+\\d+ HOLD.*/i', $due, $matches)) {
                                 $due = trim($matches[1]);
                             }
                         }
                         if (preg_match('/(\\d{2}-\\d{2}-\\d{2})/', $due, $dueMatches)) {
                             $dateDue = DateTime::createFromFormat('m-d-y', $dueMatches[1]);
                             if ($dateDue) {
                                 $dueTime = $dateDue->getTimestamp();
                             } else {
                                 $dueTime = null;
                             }
                         } else {
                             $dueTime = strtotime($due);
                         }
                         if ($dueTime != null) {
                             $daysUntilDue = ceil(($dueTime - time()) / (24 * 60 * 60));
                             $overdue = $daysUntilDue < 0;
                             $curTitle['duedate'] = $dueTime;
                             $curTitle['overdue'] = $overdue;
                             $curTitle['daysUntilDue'] = $daysUntilDue;
                         }
                         $curTitle['renewCount'] = $renewCount;
                     }
                     if (stripos($sKeys[$i], "BARCODE") > -1) {
                         $curTitle['barcode'] = strip_tags($scols[$i]);
                     }
                     if (stripos($sKeys[$i], "RENEW") > -1) {
                         $matches = array();
                         if (preg_match('/<input\\s*type="checkbox"\\s*name="renew(\\d+)"\\s*id="renew(\\d+)"\\s*value="(.*?)"\\s*\\/>/', $scols[$i], $matches)) {
                             $curTitle['canrenew'] = $patronCanRenew;
                             $curTitle['itemindex'] = $matches[1];
                             $curTitle['itemid'] = $matches[3];
                             $curTitle['renewIndicator'] = $curTitle['itemid'] . '|' . $curTitle['itemindex'];
                         } else {
                             $curTitle['canrenew'] = false;
                         }
                     }
                     if (stripos($sKeys[$i], "CALL NUMBER") > -1) {
                         $curTitle['request'] = "null";
                     }
                 }
             }
         }
         if ($sCount > 1) {
             //Get additional information from resources table
             if ($curTitle['shortId'] && strlen($curTitle['shortId']) > 0) {
                 /** @var Resource|object $resource */
                 $resource = new Resource();
                 $resource->source = 'VuFind';
                 $resource->shortId = $curTitle['shortId'];
                 if ($resource->find(true)) {
                     $timer->logTime("Found resource for " . $curTitle['shortId']);
                     $curTitle = array_merge($curTitle, get_object_vars($resource));
                     $curTitle['recordId'] = $resource->record_id;
                     $curTitle['id'] = $resource->record_id;
                 } else {
                     $timer->logTime("Did not find resource for " . $curTitle['shortId']);
                     //echo("Warning did not find resource for {$historyEntry['shortId']}");
                 }
             }
             $sortTitle = isset($curTitle['title_sort']) ? $curTitle['title_sort'] : $curTitle['title'];
             $sortKey = $sortTitle;
             if ($sortOption == 'title') {
                 $sortKey = $sortTitle;
             } elseif ($sortOption == 'author') {
                 $sortKey = (isset($curTitle['author']) ? $curTitle['author'] : "Unknown") . '-' . $sortTitle;
             } elseif ($sortOption == 'dueDate') {
                 if (isset($curTitle['duedate'])) {
                     if (preg_match('/.*?(\\d{1,2})[-\\/](\\d{1,2})[-\\/](\\d{2,4}).*/', $curTitle['duedate'], $matches)) {
                         $sortKey = $matches[3] . '-' . $matches[1] . '-' . $matches[2] . '-' . $sortTitle;
                     } else {
                         $sortKey = $curTitle['duedate'] . '-' . $sortTitle;
                     }
                 }
             } elseif ($sortOption == 'format') {
                 $sortKey = (isset($curTitle['format']) ? $curTitle['format'] : "Unknown") . '-' . $sortTitle;
             } elseif ($sortOption == 'renewed') {
                 $sortKey = (isset($curTitle['renewCount']) ? $curTitle['renewCount'] : 0) . '-' . $sortTitle;
             } elseif ($sortOption == 'holdQueueLength') {
                 $sortKey = (isset($curTitle['holdQueueLength']) ? $curTitle['holdQueueLength'] : 0) . '-' . $sortTitle;
             }
             $sortKey .= "_{$sCount}";
             $checkedOutTitles[$sortKey] = $curTitle;
         }
         $sCount++;
     }
     ksort($checkedOutTitles);
     $timer->logTime("Parsed checkout information");
     $numTransactions = count($checkedOutTitles);
     //Process pagination
     if ($recordsPerPage != -1) {
         $startRecord = ($page - 1) * $recordsPerPage;
         if ($startRecord > $numTransactions) {
             $startRecord = 0;
         }
         $checkedOutTitles = array_slice($checkedOutTitles, $startRecord, $recordsPerPage);
     }
     return array('transactions' => $checkedOutTitles, 'numTransactions' => $numTransactions);
 }
 /**
  * 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;
     }
 }