public function parseHoldsPage($pageContents)
 {
     //global $logger;
     $availableHolds = array();
     $unavailableHolds = array();
     $holds = array('available' => $availableHolds, 'unavailable' => $unavailableHolds);
     //Get the headers from the table
     preg_match_all('/<th\\s+class="patFuncHeaders">\\s*([\\w\\s]*?)\\s*<\\/th>/si', $pageContents, $result, PREG_SET_ORDER);
     $sKeys = array();
     for ($matchi = 0; $matchi < count($result); $matchi++) {
         $sKeys[] = $result[$matchi][1];
     }
     //Get the rows for the table
     preg_match_all('/<tr\\s+class="patFuncEntry(?: on_ice)?">(.*?)<\\/tr>/si', $pageContents, $result, PREG_SET_ORDER);
     $sRows = array();
     for ($matchi = 0; $matchi < count($result); $matchi++) {
         $sRows[] = $result[$matchi][1];
     }
     $sCount = 0;
     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];
         }
         //$sCols = preg_split("/<t(h|d)([^>]*)>/",$sRow);
         $curHold = array();
         $curHold['create'] = null;
         $curHold['reqnum'] = null;
         $curHold['holdSource'] = 'ILS';
         //Holds page occasionally has a header with number of items checked out.
         for ($i = 0; $i < sizeof($sCols); $i++) {
             $sCols[$i] = str_replace("&nbsp;", " ", $sCols[$i]);
             $sCols[$i] = preg_replace("/<br+?>/", " ", $sCols[$i]);
             $sCols[$i] = html_entity_decode(trim($sCols[$i]));
             //print_r($scols[$i]);
             /*if ($sCount <= 1) {
             			$sKeys[$i] = $sCols[$i];
             		} else if ($sCount > 1) {*/
             if ($sKeys[$i] == "CANCEL") {
                 //Only check Cancel key, not Cancel if not filled by
                 //Extract the id from the checkbox
                 $matches = array();
                 $numMatches = preg_match_all('/.*?cancel(.*?)x(\\d\\d).*/s', $sCols[$i], $matches);
                 if ($numMatches > 0) {
                     $curHold['renew'] = "BOX";
                     $curHold['cancelable'] = true;
                     $curHold['itemId'] = $matches[1][0];
                     $curHold['xnum'] = $matches[2][0];
                     $curHold['cancelId'] = $matches[1][0] . '~' . $matches[2][0];
                 } else {
                     $curHold['cancelable'] = false;
                 }
             }
             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] . $this->driver->getCheckDigit($shortId);
                     $title = strip_tags($matches[2]);
                 } elseif (preg_match('/.*<a href=".*?\\/record\\/C__R(.*?)\\?.*?">(.*?)<\\/a>.*/si', $sCols[$i], $matches)) {
                     $shortId = $matches[1];
                     $bibid = '.' . $matches[1] . $this->driver->getCheckDigit($shortId);
                     $title = strip_tags($matches[2]);
                 } else {
                     //This happens for prospector titles
                     $bibid = '';
                     $shortId = '';
                     $title = trim($sCols[$i]);
                     /*global $configArray;
                     		if ($configArray['System']['debug']){
                     			echo("Unexpected format in title column.  Got " . htmlentities($sCols[$i]) . "<br/>");
                     		}*/
                 }
                 $curHold['id'] = $bibid;
                 $curHold['recordId'] = $bibid;
                 $curHold['shortId'] = $shortId;
                 $curHold['title'] = $title;
             }
             if (stripos($sKeys[$i], "Ratings") > -1) {
                 $curHold['request'] = "STARS";
             }
             if (stripos($sKeys[$i], "PICKUP LOCATION") > -1) {
                 //Extract the current location for the hold if possible
                 $matches = array();
                 if (preg_match('/<select\\s+name=loc(.*?)x(\\d\\d).*?<option\\s+value="([a-z]{1,5})[+ ]*"\\s+selected="selected">.*/s', $sCols[$i], $matches)) {
                     $curHold['locationId'] = $matches[1];
                     $curHold['locationXnum'] = $matches[2];
                     $curPickupBranch = new Location();
                     $curPickupBranch->whereAdd("code = '{$matches[3]}'");
                     $curPickupBranch->find(1);
                     if ($curPickupBranch->N > 0) {
                         $curPickupBranch->fetch();
                         $curHold['currentPickupId'] = $curPickupBranch->locationId;
                         $curHold['currentPickupName'] = $curPickupBranch->displayName;
                         $curHold['location'] = $curPickupBranch->displayName;
                     }
                     $curHold['locationUpdateable'] = true;
                     //Return the full select box for reference.
                     $curHold['locationSelect'] = $sCols[$i];
                 } else {
                     $curHold['location'] = $sCols[$i];
                     //Trim the carrier code if any
                     if (preg_match('/.*\\s[\\w\\d]{4}/', $curHold['location'])) {
                         $curHold['location'] = substr($curHold['location'], 0, strlen($curHold['location']) - 5);
                     }
                     $curHold['currentPickupName'] = $curHold['location'];
                     $curHold['locationUpdateable'] = false;
                 }
             }
             if (stripos($sKeys[$i], "STATUS") > -1) {
                 $status = trim(strip_tags($sCols[$i]));
                 $status = strtolower($status);
                 $status = ucwords($status);
                 if ($status != "&nbsp") {
                     $curHold['status'] = $status;
                     if (preg_match('/READY.*(\\d{2}-\\d{2}-\\d{2})/i', $status, $matches)) {
                         $curHold['status'] = 'Ready';
                         //Get expiration date
                         $exipirationDate = $matches[1];
                         $expireDate = DateTime::createFromFormat('m-d-y', $exipirationDate);
                         $curHold['expire'] = $expireDate->getTimestamp();
                     } elseif (preg_match('/READY\\sFOR\\sPICKUP/i', $status, $matches)) {
                         $curHold['status'] = 'Ready';
                     } else {
                         $curHold['status'] = $status;
                     }
                 } else {
                     $curHold['status'] = "Pending {$status}";
                 }
                 $matches = array();
                 $curHold['renewError'] = false;
                 if (preg_match('/.*DUE\\s(\\d{2}-\\d{2}-\\d{2}).*(?:<font color="red">\\s*(.*)<\\/font>).*/s', $sCols[$i], $matches)) {
                     //Renew error
                     $curHold['renewError'] = $matches[2];
                     $curHold['statusMessage'] = $matches[2];
                 } else {
                     if (preg_match('/.*DUE\\s(\\d{2}-\\d{2}-\\d{2})\\s(.*)?/s', $sCols[$i], $matches)) {
                         $curHold['statusMessage'] = $matches[2];
                     }
                 }
                 //$logger->log('Status for item ' . $curHold['id'] . '=' . $sCols[$i], PEAR_LOG_INFO);
             }
             if (stripos($sKeys[$i], "CANCEL IF NOT FILLED BY") > -1) {
                 //$curHold['expire'] = strip_tags($scols[$i]);
             }
             if (stripos($sKeys[$i], "FREEZE") > -1) {
                 $matches = array();
                 $curHold['frozen'] = false;
                 if (preg_match('/<input.*name="freeze(.*?)"\\s*(\\w*)\\s*\\/>/', $sCols[$i], $matches)) {
                     $curHold['freezeable'] = true;
                     if (strlen($matches[2]) > 0) {
                         $curHold['frozen'] = true;
                         $curHold['status'] = 'Frozen';
                     }
                 } elseif (preg_match('/This hold can\\s?not be frozen/i', $sCols[$i], $matches)) {
                     //If we detect an error Freezing the hold, save it so we can report the error to the user later.
                     $shortId = str_replace('.b', 'b', $curHold['id']);
                     $_SESSION['freezeResult'][$shortId]['message'] = $sCols[$i];
                     $_SESSION['freezeResult'][$shortId]['result'] = false;
                 } else {
                     $curHold['freezeable'] = false;
                 }
             }
             //}
         }
         //End of columns
         //if ($sCount > 1) {
         if (!isset($curHold['status']) || strcasecmp($curHold['status'], "ready") != 0) {
             $holds['unavailable'][] = $curHold;
         } else {
             $holds['available'][] = $curHold;
         }
         //}
         $sCount++;
     }
     //End of the row
     return $holds;
 }
 private function parseReadingHistoryPage($pageContents, $patron, $sortOption, $recordsRead)
 {
     set_time_limit(60);
     //Get the headers from the table
     preg_match_all('/<th\\s+class="patFuncHeaders">\\s*(.*?)\\s*<\\/th>/si', $pageContents, $result, PREG_SET_ORDER);
     $sKeys = array();
     for ($matchi = 0; $matchi < count($result); $matchi++) {
         $sKeys[] = strip_tags($result[$matchi][1]);
     }
     //Get the rows for the table
     preg_match_all('/<tr\\s+class="patFuncEntry">(.*?)<\\/tr>/si', $pageContents, $result, PREG_SET_ORDER);
     $sRows = array();
     for ($matchi = 0; $matchi < count($result); $matchi++) {
         $sRows[] = $result[$matchi][1];
     }
     $sCount = 1;
     $readingHistoryTitles = array();
     foreach ($sRows as $sRow) {
         preg_match_all('/<td[^>]*>(.*?)<\\/td>/si', $sRow, $result, PREG_SET_ORDER);
         $sCols = array();
         for ($matchi = 0; $matchi < count($result); $matchi++) {
             $sCols[] = $result[$matchi][1];
         }
         $historyEntry = array();
         for ($i = 0; $i < sizeof($sCols); $i++) {
             $sCols[$i] = str_replace("&nbsp;", " ", $sCols[$i]);
             $sCols[$i] = preg_replace("/<br+?>/", " ", $sCols[$i]);
             $sCols[$i] = html_entity_decode(trim($sCols[$i]));
             if (stripos($sKeys[$i], "Mark") > -1) {
                 if (preg_match('/id="rsh(\\d+)"/', $sCols[$i], $matches)) {
                     $itemIndex = $matches[1];
                     $historyEntry['itemindex'] = $itemIndex;
                 }
                 $historyEntry['deletable'] = "BOX";
             }
             if (stripos($sKeys[$i], "Title") > -1) {
                 //echo("Title value is <br/>$sCols[$i]<br/>");
                 if (preg_match('/.*?<a href=\\"\\/record=(.*?)(?:~S\\d{1,2})\\">(.*?)<\\/a>.*/', $sCols[$i], $matches)) {
                     $shortId = $matches[1];
                     $bibId = '.' . $matches[1];
                     $historyEntry['id'] = $bibId;
                     $historyEntry['shortId'] = $shortId;
                 } elseif (preg_match('/.*<a href=".*?\\/record\\/C__R(.*?)\\?.*?">(.*?)<\\/a>.*/si', $sCols[$i], $matches)) {
                     $shortId = $matches[1];
                     $bibId = '.' . $matches[1] . $this->driver->getCheckDigit($shortId);
                     $historyEntry['id'] = $bibId;
                     $historyEntry['shortId'] = $shortId;
                 }
                 $title = strip_tags($sCols[$i]);
                 $historyEntry['title'] = utf8_encode($title);
             }
             if (stripos($sKeys[$i], "Author") > -1) {
                 $historyEntry['author'] = utf8_encode(strip_tags($sCols[$i]));
             }
             if (stripos($sKeys[$i], "Checked Out") > -1) {
                 $historyEntry['checkout'] = strip_tags($sCols[$i]);
             }
             if (stripos($sKeys[$i], "Details") > -1) {
                 $historyEntry['details'] = strip_tags($sCols[$i]);
             }
             if (is_array($patron)) {
                 $historyEntry['borrower_num'] = $patron['id'];
             } else {
                 $historyEntry['borrower_num'] = $patron->id;
             }
         }
         //Done processing row
         $historyEntry['title_sort'] = preg_replace('/[^a-z\\s]/', '', strtolower($historyEntry['title']));
         //$historyEntry['itemindex'] = $itemindex++;
         if ($sortOption == "title") {
             $titleKey = $historyEntry['title_sort'];
         } elseif ($sortOption == "author") {
             $titleKey = $historyEntry['author'] . "_" . $historyEntry['title_sort'];
         } elseif ($sortOption == "checkedOut" || $sortOption == "returned") {
             $checkoutTime = DateTime::createFromFormat('m-d-Y', $historyEntry['checkout']);
             if ($checkoutTime) {
                 $titleKey = $checkoutTime->getTimestamp() . "_" . $historyEntry['title_sort'];
             } else {
                 //print_r($historyEntry);
                 $titleKey = $historyEntry['title_sort'];
             }
         } elseif ($sortOption == "format") {
             $titleKey = $historyEntry['format'] . "_" . $historyEntry['title_sort'];
         } else {
             $titleKey = $historyEntry['title_sort'];
         }
         $titleKey .= '_' . ($sCount + $recordsRead);
         $readingHistoryTitles[$titleKey] = $historyEntry;
         $sCount++;
     }
     //processed all rows in the table
     return $readingHistoryTitles;
 }
 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<[^<]+?>/i", "", $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)) {
                             //Standard Millennium WebPAC
                             $shortId = $matches[1];
                             $bibid = '.' . $matches[1];
                             //Technically, this isn't correct since the check digit is missing
                             $title = strip_tags($matches[2]);
                         } elseif (preg_match('/.*<a href=".*?\\/record\\/C__R(.*?)\\?.*?">(.*?)<\\/a>.*/si', $scols[$i], $matches)) {
                             //Encore
                             $shortId = $matches[1];
                             $bibid = '.' . $matches[1];
                             //Technically, this isn't correct since the check digit is missing
                             $title = strip_tags($matches[2]);
                         } else {
                             $title = strip_tags($scols[$i]);
                             $shortId = '';
                             $bibid = '';
                         }
                         $curTitle['checkoutSource'] = 'ILS';
                         $curTitle['shortId'] = $shortId;
                         $curTitle['id'] = $bibid;
                         $curTitle['title'] = utf8_encode($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'];
                             $curTitle['renewMessage'] = '';
                         } 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) {
                 $checkDigit = $this->driver->getCheckDigit($curTitle['shortId']);
                 $curTitle['recordId'] = '.' . $curTitle['shortId'] . $checkDigit;
                 $curTitle['id'] = '.' . $curTitle['shortId'] . $checkDigit;
                 require_once ROOT_DIR . '/RecordDrivers/MarcRecord.php';
                 $recordDriver = new MarcRecord($curTitle['recordId']);
                 if ($recordDriver->isValid()) {
                     $curTitle['coverUrl'] = $recordDriver->getBookcoverUrl('medium');
                     $curTitle['groupedWorkId'] = $recordDriver->getGroupedWorkId();
                     $formats = $recordDriver->getFormats();
                     $curTitle['format'] = reset($formats);
                     $curTitle['author'] = $recordDriver->getPrimaryAuthor();
                     if (!isset($curTitle['title']) || empty($curTitle['title'])) {
                         $curTitle['title'] = $recordDriver->getTitle();
                     }
                 } else {
                     $curTitle['coverUrl'] = "";
                     $curTitle['groupedWorkId'] = "";
                     $curTitle['format'] = "Unknown";
                     $curTitle['author'] = "";
                 }
             }
             $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[utf8_encode($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);
 }