From edd21cd809d8e9e7e0fe5de739ad81eee36a5c83 Mon Sep 17 00:00:00 2001 From: Jason Cooper <scrapheap@heckrothindustries.co.uk> Date: Wed, 25 Oct 2017 14:34:05 +0100 Subject: [PATCH] Add feature to retrieve historic loans from ILS (#1031) --- config/vufind/Aleph.ini | 4 + config/vufind/Demo.ini | 11 +- config/vufind/Koha.ini | 4 + config/vufind/KohaILSDI.ini | 6 +- config/vufind/SierraRest.ini | 4 + config/vufind/config.ini | 4 + languages/en.ini | 12 ++ languages/fi.ini | 12 ++ languages/sv.ini | 12 ++ module/VuFind/config/module.config.php | 2 +- .../Controller/MyResearchController.php | 126 +++++++++++++ module/VuFind/src/VuFind/ILS/Connection.php | 23 +++ module/VuFind/src/VuFind/ILS/Driver/Aleph.php | 167 ++++++++++++++--- module/VuFind/src/VuFind/ILS/Driver/Demo.php | 156 ++++++++++++++++ module/VuFind/src/VuFind/ILS/Driver/Koha.php | 115 ++++++++++++ .../src/VuFind/ILS/Driver/KohaILSDI.php | 174 +++++++++++++++++- .../src/VuFind/ILS/Driver/MultiBackend.php | 24 +++ .../src/VuFind/ILS/Driver/SierraRest.php | 98 +++++++++- .../templates/myresearch/controls/sort.phtml | 11 ++ .../templates/myresearch/historicloans.phtml | 131 +++++++++++++ .../templates/myresearch/menu.phtml | 5 + 21 files changed, 1067 insertions(+), 34 deletions(-) create mode 100644 themes/bootstrap3/templates/myresearch/controls/sort.phtml create mode 100644 themes/bootstrap3/templates/myresearch/historicloans.phtml diff --git a/config/vufind/Aleph.ini b/config/vufind/Aleph.ini index 363e84d95b3..5d24bb17825 100644 --- a/config/vufind/Aleph.ini +++ b/config/vufind/Aleph.ini @@ -95,3 +95,7 @@ extraHoldFields = comments:requiredByDate:pickUpLocation ; \VuFind\Cache\Manager cache you would like to use for storing data ("object" is recommended). [Cache] ;type = object + +[TransactionHistory] +; By default the loan history is disabled. Uncomment the following line to enable it. +;enabled = true diff --git a/config/vufind/Demo.ini b/config/vufind/Demo.ini index 6cb15e25585..7934cbdd1e6 100644 --- a/config/vufind/Demo.ini +++ b/config/vufind/Demo.ini @@ -28,6 +28,11 @@ services[] = 'custom' ; driver. ;transactions = '[{"id":"1234", ... "renewable": true}]'; +; This setting can be used to create fake historic loan items for specific records. +; The value is a JSON document representing the status information returned by the +; driver. +;historicTransactions = '[{"id":"1234", ... "dueDate": "01/01/2017"}]'; + ; This section can be used to create a set of fake users recognized by the ; Demo driver. If it is uncommented, only usernames and passwords listed here ; will be recognized for ILS login. If it is commented out, all username/password @@ -80,4 +85,8 @@ renewMyItems = 50 ;minLength = 4 ;maxLength = 20 ;pattern = "alphanumeric" -;hint = "Your optional custom hint can go here." \ No newline at end of file +;hint = "Your optional custom hint can go here." + +[TransactionHistory] +; By default the loan history is disabled. Uncomment the following line to enable it. +;enabled = true diff --git a/config/vufind/Koha.ini b/config/vufind/Koha.ini index 46830be98f1..aff0187e84e 100644 --- a/config/vufind/Koha.ini +++ b/config/vufind/Koha.ini @@ -41,3 +41,7 @@ STAFF = "Staff Office" ;OVERDUES = false ;MANUAL = false ;DISCHARGE = false + +[TransactionHistory] +; By default the loan history is disabled. Uncomment the following line to enable it. +;enabled = true diff --git a/config/vufind/KohaILSDI.ini b/config/vufind/KohaILSDI.ini index e83ad8f4fc9..be92848d7a6 100755 --- a/config/vufind/KohaILSDI.ini +++ b/config/vufind/KohaILSDI.ini @@ -59,7 +59,7 @@ extraHoldFields = pickUpLocation ; location. By setting this to a Koha location code (e.g. '"MAIN"'), ; Vufind will default to that location. ; If no defaultPickUpLocation and no pickupLocations are defined, -; the driver will try to use the actual holdingbranch(es) of the item/title +; the driver will try to use the actual holdingbranch(es) of the item/title ; as a fallback. defaultPickUpLocation = "MAIN" @@ -89,3 +89,7 @@ pickupLocations[] = MAIN ;OVERDUES = false ;MANUAL = false ;DISCHARGE = false + +[TransactionHistory] +; By default the loan history is disabled. Uncomment the following line to enable it. +;enabled = true diff --git a/config/vufind/SierraRest.ini b/config/vufind/SierraRest.ini index 74463f14f68..4c9460a7c34 100644 --- a/config/vufind/SierraRest.ini +++ b/config/vufind/SierraRest.ini @@ -85,3 +85,7 @@ title_hold_bib_levels = a:b:m:d ; config.ini defaults when Sierra is used for authentication. ;pattern = "numeric" ;hint = "Your optional custom hint can go here." + +[TransactionHistory] +; By default the loan history is disabled. Uncomment the following line to enable it. +;enabled = true diff --git a/config/vufind/config.ini b/config/vufind/config.ini index 433914987ff..f185d63568f 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -300,6 +300,10 @@ title_level_holds_mode = "disabled" ; memory problems for users with huge numbers of items). Default = 50. ;checked_out_page_size = 50 +; The number of historic loans to display per page; 0 for no limit (may cause +; memory problems for users with a large number of historic loans). Default = 50 +;historic_loan_page_size = 50 + ; Whether to display the item barcode for each loan. Default is false. ;display_checked_out_item_barcode = true diff --git a/languages/en.ini b/languages/en.ini index ce747a8b8da..1312796757f 100644 --- a/languages/en.ini +++ b/languages/en.ini @@ -183,6 +183,7 @@ Check Recall = "Check Recall" Checked Out = "Checked Out" Checked Out Items = "Checked Out Items" Checkedout = "Checked Out" +Checkout Date = "Checkout Date" Chicago Citation = "Chicago Style Citation" child_record_count = "%%count%% records" child_records = "Contents/pieces" @@ -501,12 +502,14 @@ ill_request_processed = "Processed" ill_request_profile_html = "For interlibrary loan request information, please establish your <a href="%%url%%">Library Catalog Profile</a>." ill_request_submit_text = "Place Request" Illustrated = "Illustrated" +ils_action_unavailable = "The requested function is not available with the active library card." ils_connection_failed = "Connection to the library management system failed. Information related to your library account cannot be displayed. If the problem persists, please contact your library." ils_offline_holdings_message = "Holdings and item availability information is currently unavailable. Please accept our apologies for any inconvenience this may cause and contact us for further assistance:" ils_offline_home_message = "Your account details and live item information will be unavailable during this time. Please accept our apologies for any inconvenience this may cause and contact us for further assistance:" ils_offline_login_message = "Your account details will be unavailable during this time. Please accept our apologies for any inconvenience this may cause and contact us for further assistance:" ils_offline_status = "Our Library Management System is currently under maintenance." ils_offline_title = "System Under Maintenance" +ils_transaction_history_disabled = "Loan history is not enabled for the active library card." Import Record = "Import Record" in = "in" In This Collection = "In This Collection" @@ -581,6 +584,8 @@ list_access_denied = "You do not have permission to view this list." list_edit_name_required = "List name is required." load_tag_error = "Error: Could Not Load Tags" Loading = "Loading" +Loan History = "Loan History" +loan_history_empty = "You do not have any loans in the loan history." Local Login = "Local Login" local_login_desc = "Enter the username and password you created for this site." Located = "Located" @@ -840,6 +845,7 @@ results = "results" Results for = "Results for" Results per page = "Results per page" Resumption Token = "Resumption Token" +Return Date = "Return Date" Review by = "Review by" Reviews = "Reviews" Save = "Save" @@ -911,8 +917,14 @@ sort_author = "Author" sort_author_author = "Alphabetical" sort_author_relevance = "Popularity" sort_callnumber = "Call Number" +sort_checkout_date_asc = "Checkout Date (oldest first)" +sort_checkout_date_desc = "Checkout Date (newest first)" sort_count = "Result Count" +sort_due_date_asc = "Due Date (oldest first)" +sort_due_date_desc = "Due Date (newest first)" sort_relevance = "Relevance" +sort_return_date_asc = "Return Date (oldest first)" +sort_return_date_desc = "Return Date (newest first)" sort_title = "Title" sort_year = "Date Descending" sort_year asc = "Date Ascending" diff --git a/languages/fi.ini b/languages/fi.ini index e0d332f2b9b..438ca2a41d9 100644 --- a/languages/fi.ini +++ b/languages/fi.ini @@ -182,6 +182,7 @@ Check Recall = "Tarkista varaus" Checked Out = "Lainat" Checked Out Items = "Lainat" Checkedout = "Lainat" +Checkout Date = "Lainauspäivä" Chicago Citation = "Chicago-tyylinen lähdeviittaus" child_record_count = "%%count%% tietuetta" child_records = "Sisältö/kappaleet" @@ -506,12 +507,14 @@ ill_request_processed = "Käsitelty" ill_request_profile_html = "Kirjaudu <a href="%%url%%">kirjastokortilla</a> nähdäksesi kaukolainatilaukset." ill_request_submit_text = "Tee kaukolainatilaus" Illustrated = "Kuvitus" +ils_action_unavailable = "Toiminto ei ole saatavissa käytössä olevalla kirjastokortilla." ils_connection_failed = "Kirjastojärjestelmään ei saatu yhteyttä. Tietoja, jotka liittyvät tiliisi kirjastossa, ei voida näyttää. Jos ongelma jatkuu, ota yhteyttä kirjastoon." ils_offline_holdings_message = "Saatavuustiedot eivät ole juuri nyt käytettävissä. Pahoittelemme tästä aiheutunutta vaivaa. Voitte ottaa yhteyttä:" ils_offline_home_message = "Tilitietosi ja ajantasaiset saatavuustiedot ovat poissa käytöstä tämän ajan. Pahoittelemme tästä aiheutunutta vaivaa. Voitte ottaa yhteyttä:" ils_offline_login_message = "Tilitietosi ovat poissa käytöstä tämän ajan. Pahoittelemme tästä aiheutunutta vaivaa. Voitte ottaa yhteyttä:" ils_offline_status = "Kirjastojärjestelmä on juuri nyt pois käytöstä." ils_offline_title = "Järjestelmä pois käytöstä" +ils_transaction_history_disabled = "Lainaushistoriaa ei ole otettu käyttöön valitulla kirjastokortilla." Import Record = "Tuo tietue" in = "kentästä" In This Collection = "Tässä kokoelmassa" @@ -586,6 +589,8 @@ list_access_denied = "Sinulla ei ole oikeuksia katsoa tätä listaa." list_edit_name_required = "Listan nimi tarvitaan." load_tag_error = "Virhe: Tagien lataaminen epäonnistui" Loading = "Lataa" +Loan History = "Lainaushistoria" +loan_history_empty = "Ei tietoja lainaushistoriassa." Local Login = "Paikallinen kirjautuminen" local_login_desc = "Syötä käyttäjätunnus ja salasana, jotka loit tätä sivustoa varten." Located = "Sijainti" @@ -845,6 +850,7 @@ results = "tuloksesta" Results for = "Tulokset haulle" Results per page = "Tuloksia sivulla" Resumption Token = "Resumption Token" +Return Date = "Palautuspäivä" Review by = "Arvostellut" Reviews = "Arvostelut" Save = "Tallenna" @@ -916,8 +922,14 @@ sort_author = "Tekijä" sort_author_author = "Aakkosellinen" sort_author_relevance = "Relevanssi" sort_callnumber = "Luokka" +sort_checkout_date_asc = "Lainauspäivä (vanhin ensin)" +sort_checkout_date_desc = "Lainauspäivä (uusin ensin)" sort_count = "Lukumäärä" +sort_due_date_asc = "Eräpäivä (vanhin ensin)" +sort_due_date_desc = "Eräpäivä (uusin ensin)" sort_relevance = "Relevanssi" +sort_return_date_asc = "Palautuspäivä (vanhin ensin)" +sort_return_date_desc = "Palautuspäivä (uusin ensin)" sort_title = "Nimeke" sort_year = "Aika (uusimmat ensin)" sort_year asc = "Aika (vanhimmat ensin)" diff --git a/languages/sv.ini b/languages/sv.ini index 4fb42fb7113..41341c82f73 100644 --- a/languages/sv.ini +++ b/languages/sv.ini @@ -182,6 +182,7 @@ Check Recall = "Kolla återkallelse" Checked Out = "Lån" Checked Out Items = "Lån" Checkedout = "Lån" +Checkout Date = "Utlåningsdag" Chicago Citation = "Chicago-stil citat" child_record_count = "%%count%% poster" child_records = "Innehåll/delar" @@ -501,12 +502,14 @@ ill_request_processed = "Behandlad" ill_request_profile_html = "Logga in med din <a href="%%url%%">bibliotekskort</a> för att se fjärrlånbeställningar." ill_request_submit_text = "Beställ" Illustrated = "Illustrerad" +ils_action_unavailable = "Den begärda åtgärden är inte tillgänglig med det aktiva bibliotekskortet." ils_connection_failed = "Anslutning till bibliotekssystemet misslyckades. Information relaterad till ditt bibliotekskonto kan inte visas. Kontakta kundtjänst om problemet kvarstår." ils_offline_holdings_message = "Holdings and item availability information is currently unavailable. Please accept our apologies for any inconvenience this may cause and contact us for further assistance:" ils_offline_home_message = "Your account details and live item information will be unavailable during this time. Please accept our apologies for any inconvenience this may cause and contact us for further assistance:" ils_offline_login_message = "Your account details will be unavailable during this time. Please accept our apologies for any inconvenience this may cause and contact us for further assistance:" ils_offline_status = "Our Library Management System is currently under maintenance." ils_offline_title = "System Under Maintenance" +ils_transaction_history_disabled = "Utlåningshistoriken är inte aktiverat för det aktiva bibliotekskortet." Import Record = "Importera posten" in = "i fältet" In This Collection = "I denna samling" @@ -581,6 +584,8 @@ list_access_denied = "Du har inte rättigheter att se denna lista." list_edit_name_required = "Namn på lista behövs." load_tag_error = "Fel: taggar kunde inte hämtas." Loading = "Laddar" +Loan History = "Utlåningshistorik" +loan_history_empty = "Du har inga lån i utlåningshistoriken." Local Login = "Lokal login" local_login_desc = "Ange användarnamn och lösenord du skapade för den här webbplatsen." Located = "Placering" @@ -840,6 +845,7 @@ results = "resultat" Results for = "Resultat för sökningen" Results per page = "Resultat per sida" Resumption Token = "Resumption Token" +Return Date = "Återlämningsdag" Review by = "Recensent" Reviews = "Recensioner" Save = "Spara" @@ -911,8 +917,14 @@ sort_author = "Upphovsman" sort_author_author = "Alfabetiskt" sort_author_relevance = "Relevans" sort_callnumber = "Signum" +sort_checkout_date_asc = "Utlåningsdag (äldst först)" +sort_checkout_date_desc = "Utlåningsdag (nyast först)" sort_count = "Antal" +sort_due_date_asc = "Förfallodag (äldst först)" +sort_due_date_desc = "Förfallodag (nyast först)" sort_relevance = "Relevans" +sort_return_date_asc = "Returneringsdag (äldst först)" +sort_return_date_desc = "Returneringsdag (nyast först)" sort_title = "Titel" sort_year = "Tid (nyaste först)" sort_year asc = "Tid (äldsta först)" diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index f08f0503826..e62fbeb6145 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -848,7 +848,7 @@ $staticRoutes = [ 'MyResearch/Account', 'MyResearch/ChangePassword', 'MyResearch/CheckedOut', 'MyResearch/Delete', 'MyResearch/DeleteList', 'MyResearch/Edit', 'MyResearch/Email', 'MyResearch/Favorites', 'MyResearch/Fines', - 'MyResearch/Holds', 'MyResearch/Home', + 'MyResearch/HistoricLoans', 'MyResearch/Holds', 'MyResearch/Home', 'MyResearch/ILLRequests', 'MyResearch/Logout', 'MyResearch/NewPassword', 'MyResearch/Profile', 'MyResearch/Recover', 'MyResearch/SaveSearch', diff --git a/module/VuFind/src/VuFind/Controller/MyResearchController.php b/module/VuFind/src/VuFind/Controller/MyResearchController.php index 86e18221c1d..9f2240f8bea 100644 --- a/module/VuFind/src/VuFind/Controller/MyResearchController.php +++ b/module/VuFind/src/VuFind/Controller/MyResearchController.php @@ -1211,6 +1211,132 @@ class MyResearchController extends AbstractBase ); } + /** + * Send list of historic loans to view + * + * @return mixed + */ + public function historicloansAction() + { + // Stop now if the user does not have valid catalog credentials available: + if (!is_array($patron = $this->catalogLogin())) { + return $patron; + } + + // Connect to the ILS: + $catalog = $this->getILS(); + + // Check function config + $functionConfig = $catalog->checkFunction( + 'getMyTransactionHistory', $patron + ); + if (false === $functionConfig) { + $this->flashMessenger()->addErrorMessage('ils_action_unavailable'); + return $this->createViewModel(); + } + + // Get page and page size: + $page = (int)$this->params()->fromQuery('page', 1); + $config = $this->getConfig(); + $limit = isset($config->Catalog->historic_loan_page_size) + ? $config->Catalog->historic_loan_page_size : 50; + $ilsPaging = true; + if (isset($functionConfig['max_results'])) { + $limit = min([$functionConfig['max_results'], $limit]); + } elseif (isset($functionConfig['page_size'])) { + if (!in_array($limit, $functionConfig['page_size'])) { + $limit = isset($functionConfig['default_page_size']) + ? $functionConfig['default_page_size'] + : $functionConfig['page_size'][0]; + } + } else { + $ilsPaging = false; + } + + // Get sort settings + $sort = false; + if (!empty($functionConfig['sort'])) { + $sort = $this->params()->fromQuery('sort'); + if (!isset($functionConfig['sort'][$sort])) { + if (isset($functionConfig['default_sort'])) { + $sort = $functionConfig['default_sort']; + } else { + reset($functionConfig['sort']); + $sort = key($functionConfig['sort']); + } + } + } + + // Configure call params + $params = [ + 'sort' => $sort + ]; + if ($ilsPaging) { + $params['page'] = $page; + $params['limit'] = $limit; + } + + // Get checked out item details: + $result = $catalog->getMyTransactionHistory($patron, $params); + + if (isset($result['success']) && !$result['success']) { + $this->flashMessenger()->addErrorMessage($result['status']); + return $this->createViewModel(); + } + + // Build paginator if needed: + if ($ilsPaging && $limit < $result['count']) { + $adapter = new \Zend\Paginator\Adapter\NullFill($result['count']); + $paginator = new \Zend\Paginator\Paginator($adapter); + $paginator->setItemCountPerPage($limit); + $paginator->setCurrentPageNumber($page); + $pageStart = $paginator->getAbsoluteItemNumber(1) - 1; + $pageEnd = $paginator->getAbsoluteItemNumber($limit) - 1; + } elseif ($limit > 0 && $limit < $result['count']) { + $adapter = new \Zend\Paginator\Adapter\ArrayAdapter( + $result['transactions'] + ); + $paginator = new \Zend\Paginator\Paginator($adapter); + $paginator->setItemCountPerPage($limit); + $paginator->setCurrentPageNumber($page); + $pageStart = $paginator->getAbsoluteItemNumber(1) - 1; + $pageEnd = $paginator->getAbsoluteItemNumber($limit) - 1; + } else { + $paginator = false; + $pageStart = 0; + $pageEnd = $result['count']; + } + + $transactions = $hiddenTransactions = []; + foreach ($result['transactions'] as $i => $current) { + // Build record driver (only for the current visible page): + if ($ilsPaging || ($i >= $pageStart && $i <= $pageEnd)) { + $transactions[] = $this->getDriverForILSRecord($current); + } else { + $hiddenTransactions[] = $current; + } + } + + // Handle view params for sorting + $sortList = []; + if (!empty($functionConfig['sort'])) { + foreach ($functionConfig['sort'] as $key => $value) { + $sortList[$key] = [ + 'desc' => $value, + 'url' => '?sort=' . urlencode($key), + 'selected' => $sort == $key + ]; + } + } + + return $this->createViewModel( + compact( + 'transactions', 'paginator', 'params', + 'hiddenTransactions', 'sortList', 'functionConfig' + ) + ); + } + /** * Send list of fines to view * diff --git a/module/VuFind/src/VuFind/ILS/Connection.php b/module/VuFind/src/VuFind/ILS/Connection.php index bdb1227d25a..3cf0ece8ce4 100644 --- a/module/VuFind/src/VuFind/ILS/Connection.php +++ b/module/VuFind/src/VuFind/ILS/Connection.php @@ -612,6 +612,29 @@ class Connection implements TranslatorAwareInterface, LoggerAwareInterface return false; } + /** + * Check Historic Loans + * + * A support method for checkFunction(). This is responsible for checking + * the driver configuration to determine if the system supports historic + * loans. + * + * @param array $functionConfig Function configuration + * @param array $params Patron data + * + * @return mixed On success, an associative array with specific function keys + * and values; on failure, false. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function checkMethodgetMyTransactionHistory($functionConfig, $params) + { + if ($this->checkCapability('getMyTransactionHistory', [$params ?: []])) { + return $functionConfig; + } + return false; + } + /** * Get proper help text from the function config * diff --git a/module/VuFind/src/VuFind/ILS/Driver/Aleph.php b/module/VuFind/src/VuFind/ILS/Driver/Aleph.php index d2aecbb189d..bc84ca594bf 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Aleph.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Aleph.php @@ -842,17 +842,106 @@ class Aleph extends AbstractBase implements \Zend\Log\LoggerAwareInterface, } /** - * Get Patron Transaction History + * Get Patron Loan History * - * @param array $user The patron array from patronLogin + * @param array $user The patron array from patronLogin + * @param array $params Parameters * * @throws \VuFind\Exception\Date * @throws ILSException - * @return array Array of the patron's transactions on success. + * @return array Array of the patron's historic loans on success. */ - public function getMyHistory($user) + public function getMyTransactionHistory($user, $params = null) { - return $this->getMyTransactions($user, true); + $userId = $user['id']; + $historicLoans = []; + $requestParams = [ + "view" => "full", + "type" => "history", + ]; + + $xml = $this->doRestDLFRequest( + ['patron', $userId, 'circulationActions', 'loans'], $requestParams + ); + + foreach ($xml->xpath('//loan') as $item) { + $z36h = $item->z36h; + $z13 = $item->z13; + $z30 = $item->z30; + $group = $item->xpath('@href'); + $group = substr(strrchr($group[0], "/"), 1); + $location = (string)$z36h->{'z36_pickup_location'}; + $reqnum = (string)$z36h->{'z36-doc-number'} + . (string)$z36h->{'z36-item-sequence'} + . (string)$z36h->{'z36-sequence'}; + + $due = (string)$z36h->{'z36h-due-date'}; + $returned = (string)$z36h->{'z36h-returned-date'}; + $issued = (string)$z36h->{'z36h-loan-date'}; + $title = (string)$z13->{'z13-title'}; + $author = (string)$z13->{'z13-author'}; + $isbn = (string)$z13->{'z13-isbn-issn'}; + $barcode = (string)$z30->{'z30-barcode'}; + + $historicLoans[] = [ + 'id' => (string)$z30->{'z30-doc-number'}, + 'item_id' => $group, + 'location' => $location, + 'title' => $title, + 'author' => $author, + 'isbn' => [$isbn], + 'reqnum' => $reqnum, + 'barcode' => $barcode, + 'checkoutDate' => $this->parseDate($issued), + 'dueDate' => $this->parseDate($due), + 'returnDate' => $this->parseDate($returned), + '_checkoutDate' => $issued, + '_dueDate' => $due, + '_returnDate' => $returned, + ]; + } + + if (isset($params['sort'])) { + switch ($params['sort']) { + case 'checkout asc': + $sorter = function ($a, $b) { + return strcmp($a['_checkoutDate'], $b['_checkoutDate']); + }; + break; + case 'return desc': + $sorter = function ($a, $b) { + return strcmp($b['_returnDate'], $a['_returnDate']); + }; + break; + case 'return asc': + $sorter = function ($a, $b) { + return strcmp($a['_returnDate'], $b['_returnDate']); + }; + break; + case 'due desc': + $sorter = function ($a, $b) { + return strcmp($b['_dueDate'], $a['_dueDate']); + }; + break; + case 'due asc': + $sorter = function ($a, $b) { + return strcmp($a['_dueDate'], $b['_dueDate']); + }; + break; + default: + $sorter = function ($a, $b) { + return strcmp($b['_checkoutDate'], $a['_checkoutDate']); + }; + break; + } + + usort($historicLoans, $sorter); + } + + return [ + 'count' => count($historicLoans), + 'transactions' => $historicLoans, + ]; } /** @@ -861,25 +950,21 @@ class Aleph extends AbstractBase implements \Zend\Log\LoggerAwareInterface, * This is responsible for retrieving all transactions (i.e. checked out items) * by a specific patron. * - * @param array $user The patron array from patronLogin - * @param bool $history Include history of transactions (true) or just get - * current ones (false). + * @param array $user The patron array from patronLogin * * @throws \VuFind\Exception\Date * @throws ILSException * @return array Array of the patron's transactions on success. */ - public function getMyTransactions($user, $history = false) + public function getMyTransactions($user) { $userId = $user['id']; $transList = []; $params = ["view" => "full"]; - if ($history) { - $params["type"] = "history"; - } $xml = $this->doRestDLFRequest( ['patron', $userId, 'circulationActions', 'loans'], $params ); + foreach ($xml->xpath('//loan') as $item) { $z36 = $item->z36; $z13 = $item->z13; @@ -890,25 +975,22 @@ class Aleph extends AbstractBase implements \Zend\Log\LoggerAwareInterface, //$docno = (string) $z36->{'z36-doc-number'}; //$itemseq = (string) $z36->{'z36-item-sequence'}; //$seq = (string) $z36->{'z36-sequence'}; + $location = (string)$z36->{'z36_pickup_location'}; $reqnum = (string)$z36->{'z36-doc-number'} . (string)$z36->{'z36-item-sequence'} . (string)$z36->{'z36-sequence'}; - $due = $returned = null; - if ($history) { - $due = $item->z36h->{'z36h-due-date'}; - $returned = $item->z36h->{'z36h-returned-date'}; - } else { - $due = (string)$z36->{'z36-due-date'}; - } - //$loaned = (string) $z36->{'z36-loan-date'}; + + $due = (string)$z36->{'z36-due-date'}; + $issued = (string)$z36->{'z36-loan-date'}; $title = (string)$z13->{'z13-title'}; $author = (string)$z13->{'z13-author'}; $isbn = (string)$z13->{'z13-isbn-issn'}; $barcode = (string)$z30->{'z30-barcode'}; + $transList[] = [ //'type' => $type, - 'id' => ($history) ? null : $this->barcodeToID($barcode), + 'id' => $this->barcodeToID($barcode), 'item_id' => $group, 'location' => $location, 'title' => $title, @@ -916,14 +998,15 @@ class Aleph extends AbstractBase implements \Zend\Log\LoggerAwareInterface, 'isbn' => [$isbn], 'reqnum' => $reqnum, 'barcode' => $barcode, + 'issuedate' => $this->parseDate($issued), 'duedate' => $this->parseDate($due), - 'returned' => $this->parseDate($returned), //'holddate' => $holddate, //'delete' => $delete, 'renewable' => true, //'create' => $this->parseDate($create) ]; } + return $transList; } @@ -1584,8 +1667,29 @@ class Aleph extends AbstractBase implements \Zend\Log\LoggerAwareInterface, } /** - * Public Function which retrieves renew, hold and cancel settings from the - * driver ini file. + * Helper method to determine whether or not a certain method can be + * called on this driver. Required method for any smart drivers. + * + * @param string $method The name of the called method. + * @param array $params Array of passed parameters + * + * @return bool True if the method can be called with the given parameters, + * false otherwise. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function supportsMethod($method, $params) + { + // Loan history is only available if properly configured + if ($method == 'getMyTransactionHistory') { + return !empty($this->config['TransactionHistory']['enabled']); + } + return is_callable([$this, $method]); + } + + /** + * Public Function which retrieves historic loan, renew, hold and cancel + * settings from the driver ini file. * * @param string $func The name of the feature to be checked * @param array $params Optional feature-specific parameters (array) @@ -1605,6 +1709,21 @@ class Aleph extends AbstractBase implements \Zend\Log\LoggerAwareInterface, "extraHoldFields" => "comments:requiredByDate:pickUpLocation", "defaultRequiredDate" => "0:1:0" ]; + } elseif ('getMyTransactionHistory' === $func) { + if (empty($this->config['TransactionHistory']['enabled'])) { + return false; + } + return [ + 'sort' => [ + 'checkout desc' => 'sort_checkout_date_desc', + 'checkout asc' => 'sort_checkout_date_asc', + 'return desc' => 'sort_return_date_desc', + 'return asc' => 'sort_return_date_asc', + 'due desc' => 'sort_due_date_desc', + 'due asc' => 'sort_due_date_asc' + ], + 'default_sort' => 'checkout desc', + ]; } else { return []; } diff --git a/module/VuFind/src/VuFind/ILS/Driver/Demo.php b/module/VuFind/src/VuFind/ILS/Driver/Demo.php index 9ce6eeb8007..87d83b20416 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Demo.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Demo.php @@ -972,6 +972,145 @@ class Demo extends AbstractBase return $session->transactions; } + /** + * Construct a historic transaction list for getMyTransactionHistory; may be + * random or pre-set depending on Demo.ini settings. + * + * @return array + */ + protected function getHistoricTransactionList() + { + $this->checkIntermittentFailure(); + // If Demo.ini includes a fixed set of transactions, load those; otherwise + // build some random ones. + return isset($this->config['Records']['historicTransactions']) + ? json_decode($this->config['Records']['historicTransactions'], true) + : $this->getRandomHistoricTransactionList(); + } + + /** + * Construct a random set of transactions for getMyTransactionHistory(). + * + * @return array + */ + protected function getRandomHistoricTransactionList() + { + // How many items are there? %10 - 1 = 10% chance of none, + // 90% of 1-150 (give or take some odd maths) + $trans = rand() % 10 - 1 > 0 ? rand() % 15 : 0; + + $transList = []; + for ($i = 0; $i < $trans; $i++) { + // Checkout date + $relative = rand() % 300; + $checkoutDate = strtotime("now -$relative days"); + // Due date (7-30 days from checkout) + $dueDate = $checkoutDate + 60 * 60 * 24 * (rand() % 23 + 7); + // Return date (1-40 days from checkout and < now) + $returnDate = min( + [$checkoutDate + 60 * 60 * 24 * (rand() % 39 + 1), time()] + ); + + // Create a generic transaction: + $transList[] = $this->getRandomItemIdentifier() + [ + 'checkoutDate' => $this->dateConverter->convertToDisplayDate( + 'U', $checkoutDate + ), + 'dueDate' => $this->dateConverter->convertToDisplayDate( + 'U', $dueDate + ), + 'returnDate' => $this->dateConverter->convertToDisplayDate( + 'U', $returnDate + ), + // Raw dates for sorting + '_checkoutDate' => $checkoutDate, + '_dueDate' => $dueDate, + '_returnDate' => $returnDate, + 'barcode' => sprintf("%08d", rand() % 50000), + 'item_id' => $i, + ]; + if ($this->idsInMyResearch) { + $transList[$i]['id'] = $this->getRandomBibId(); + $transList[$i]['source'] = $this->getRecordSource(); + } else { + $transList[$i]['title'] = 'Demo Title ' . $i; + } + } + return $transList; + } + + /** + * Get Patron Loan History + * + * This is responsible for retrieving all historic transactions for a specific + * patron. + * + * @param array $patron The patron array from patronLogin + * @param array $params Parameters + * + * @return mixed Array of the patron's historic transactions on success. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getMyTransactionHistory($patron, $params) + { + $this->checkIntermittentFailure(); + $session = $this->getSession(); + if (!isset($session->historicLoans)) { + $session->historicLoans = $this->getHistoricTransactionList(); + } + + // Sort and splice the list + $historicLoans = $session->historicLoans; + if (isset($params['sort'])) { + switch ($params['sort']) { + case 'checkout asc': + $sorter = function ($a, $b) { + return strcmp($a['_checkoutDate'], $b['_checkoutDate']); + }; + break; + case 'return desc': + $sorter = function ($a, $b) { + return strcmp($b['_returnDate'], $a['_returnDate']); + }; + break; + case 'return asc': + $sorter = function ($a, $b) { + return strcmp($a['_returnDate'], $b['_returnDate']); + }; + break; + case 'due desc': + $sorter = function ($a, $b) { + return strcmp($b['_dueDate'], $a['_dueDate']); + }; + break; + case 'due asc': + $sorter = function ($a, $b) { + return strcmp($a['_dueDate'], $b['_dueDate']); + }; + break; + default: + $sorter = function ($a, $b) { + return strcmp($b['_checkoutDate'], $a['_checkoutDate']); + }; + break; + } + + usort($historicLoans, $sorter); + } + + $limit = isset($params['limit']) ? (int)$params['limit'] : 50; + $start = isset($params['page']) + ? ((int)$params['page'] - 1) * $limit : 0; + + $historicLoans = array_splice($historicLoans, $start, $limit); + + return [ + 'count' => count($session->historicLoans), + 'transactions' => $historicLoans + ]; + } + /** * Get Pick Up Locations * @@ -2036,6 +2175,23 @@ class Demo extends AbstractBase ? $this->config['changePassword'] : ['minLength' => 4, 'maxLength' => 20]; } + if ($function == 'getMyTransactionHistory') { + if (empty($this->config['TransactionHistory']['enabled'])) { + return false; + } + return [ + 'max_results' => 100, + 'sort' => [ + 'checkout desc' => 'sort_checkout_date_desc', + 'checkout asc' => 'sort_checkout_date_asc', + 'return desc' => 'sort_return_date_desc', + 'return asc' => 'sort_return_date_asc', + 'due desc' => 'sort_due_date_desc', + 'due asc' => 'sort_due_date_asc' + ], + 'default_sort' => 'checkout desc' + ]; + } return []; } } diff --git a/module/VuFind/src/VuFind/ILS/Driver/Koha.php b/module/VuFind/src/VuFind/ILS/Driver/Koha.php index 71728dba21f..32b0840a388 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Koha.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Koha.php @@ -484,6 +484,89 @@ class Koha extends AbstractBase return count($blocks) ? $blocks : false; } + /** + * Get Patron Loan History + * + * This is responsible for retrieving all historic loans (i.e. items previously + * checked out and then returned), for a specific patron. + * + * @param array $patron The patron array from patronLogin + * @param array $params Parameters + * + * @throws \VuFind\Exception\Date + * @throws ILSException + * @return array Array of the patron's transactions on success. + */ + public function getMyTransactionHistory($patron, $params) + { + $id = 0; + $historicLoans = []; + $row = $sql = $sqlStmt = ''; + try { + if (!$this->db) { + $this->initDb(); + } + $id = $patron['id']; + + // Get total count first + $sql = "select count(*) as cnt from old_issues " . + "where old_issues.borrowernumber = :id"; + $sqlStmt = $this->db->prepare($sql); + $sqlStmt->execute([':id' => $id]); + $totalCount = $sqlStmt->fetch()['cnt']; + + // Get rows + $limit = isset($params['limit']) ? (int)$params['limit'] : 50; + $start = isset($params['page']) + ? ((int)$params['page'] - 1) * $limit : 0; + if (isset($params['sort'])) { + $parts = explode(' ', $params['sort'], 2); + switch ($parts[0]) { + case 'return': + $sort = 'RETURNED'; + break; + case 'due': + $sort = 'DUEDATE'; + break; + default: + $sort = 'ISSUEDATE'; + break; + } + $sort .= isset($parts[1]) && 'asc' === $parts[1] ? ' asc' : ' desc'; + } else { + $sort = 'ISSUEDATE desc'; + } + $sql = "select old_issues.issuedate as ISSUEDATE, " . + "old_issues.date_due as DUEDATE, items.biblionumber as " . + "BIBNO, items.barcode BARCODE, old_issues.returndate as RETURNED, " . + "biblio.title as TITLE " . + "from old_issues join items " . + "on old_issues.itemnumber = items.itemnumber " . + "join biblio on items.biblionumber = biblio.biblionumber " . + "where old_issues.borrowernumber = :id " . + "order by $sort limit $start,$limit"; + $sqlStmt = $this->db->prepare($sql); + + $sqlStmt->execute([':id' => $id]); + foreach ($sqlStmt->fetchAll() as $row) { + $historicLoans[] = [ + 'title' => $row['TITLE'], + 'checkoutDate' => $this->displayDateTime($row['ISSUEDATE']), + 'dueDate' => $this->displayDateTime($row['DUEDATE']), + 'id' => $row['BIBNO'], + 'barcode' => $row['BARCODE'], + 'returnDate' => $this->displayDateTime($row['RETURNED']), + ]; + } + return [ + 'count' => $totalCount, + 'transactions' => $historicLoans + ]; + } catch (PDOException $e) { + throw new ILSException($e->getMessage()); + } + } + /** * Get Purchase History * @@ -681,4 +764,36 @@ class Koha extends AbstractBase return $date; } } + + /** + * Public Function which retrieves renew, hold and cancel settings from the + * driver ini file. + * + * @param string $function The name of the feature to be checked + * + * @return array An array with key-value pairs. + */ + public function getConfig($function) + { + if ('getMyTransactionHistory' === $function) { + if (empty($this->config['TransactionHistory']['enabled'])) { + return false; + } + return [ + 'max_results' => 100, + 'sort' => [ + 'checkout desc' => 'sort_checkout_date_desc', + 'checkout asc' => 'sort_checkout_date_asc', + 'return desc' => 'sort_return_date_desc', + 'return asc' => 'sort_return_date_asc', + 'due desc' => 'sort_due_date_desc', + 'due asc' => 'sort_due_date_asc' + ], + 'default_sort' => 'checkout desc' + ]; + } + return isset($this->config[$function]) + ? $this->config[$function] + : false; + } } diff --git a/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php b/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php index 9e868034153..685a22e09b2 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php +++ b/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php @@ -511,13 +511,26 @@ class KohaILSDI extends \VuFind\ILS\Driver\AbstractBase implements */ public function getConfig($function) { - $functionConfig = ""; - if (isset($this->config[$function])) { - $functionConfig = $this->config[$function]; - } else { - $functionConfig = false; + if ('getMyTransactionHistory' === $function) { + if (empty($this->config['TransactionHistory']['enabled'])) { + return false; + } + return [ + 'max_results' => 100, + 'sort' => [ + 'checkout desc' => 'sort_checkout_date_desc', + 'checkout asc' => 'sort_checkout_date_asc', + 'return desc' => 'sort_return_date_desc', + 'return asc' => 'sort_return_date_asc', + 'due desc' => 'sort_due_date_desc', + 'due asc' => 'sort_due_date_asc' + ], + 'default_sort' => 'checkout desc' + ]; } - return $functionConfig; + return isset($this->config[$function]) + ? $this->config[$function] + : false; } /** @@ -1382,6 +1395,89 @@ class KohaILSDI extends \VuFind\ILS\Driver\AbstractBase implements return count($blocks) ? $blocks : false; } + /** + * Get Patron Loan History + * + * This is responsible for retrieving all historic loans (i.e. items previously + * checked out and then returned), for a specific patron. + * + * @param array $patron The patron array from patronLogin + * @param array $params Parameters + * + * @throws \VuFind\Exception\Date + * @throws ILSException + * @return array Array of the patron's transactions on success. + */ + public function getMyTransactionHistory($patron, $params) + { + $id = 0; + $historicLoans = []; + $row = $sql = $sqlStmt = ''; + try { + if (!$this->db) { + $this->initDb(); + } + $id = $patron['id']; + + // Get total count first + $sql = "select count(*) as cnt from old_issues " . + "where old_issues.borrowernumber = :id"; + $sqlStmt = $this->db->prepare($sql); + $sqlStmt->execute([':id' => $id]); + $totalCount = $sqlStmt->fetch()['cnt']; + + // Get rows + $limit = isset($params['limit']) ? (int)$params['limit'] : 50; + $start = isset($params['page']) + ? ((int)$params['page'] - 1) * $limit : 0; + if (isset($params['sort'])) { + $parts = explode(' ', $params['sort'], 2); + switch ($parts[0]) { + case 'return': + $sort = 'RETURNED'; + break; + case 'due': + $sort = 'DUEDATE'; + break; + default: + $sort = 'ISSUEDATE'; + break; + } + $sort .= isset($parts[1]) && 'asc' === $parts[1] ? ' asc' : ' desc'; + } else { + $sort = 'ISSUEDATE desc'; + } + $sql = "select old_issues.issuedate as ISSUEDATE, " . + "old_issues.date_due as DUEDATE, items.biblionumber as " . + "BIBNO, items.barcode BARCODE, old_issues.returndate as RETURNED, " . + "biblio.title as TITLE " . + "from old_issues join items " . + "on old_issues.itemnumber = items.itemnumber " . + "join biblio on items.biblionumber = biblio.biblionumber " . + "where old_issues.borrowernumber = :id " . + "order by $sort limit $start,$limit"; + $sqlStmt = $this->db->prepare($sql); + + $sqlStmt->execute([':id' => $id]); + foreach ($sqlStmt->fetchAll() as $row) { + $historicLoans[] = [ + 'title' => $row['TITLE'], + 'checkoutDate' => $this->displayDateTime($row['ISSUEDATE']), + 'dueDate' => $this->displayDateTime($row['DUEDATE']), + 'id' => $row['BIBNO'], + 'barcode' => $row['BARCODE'], + 'returnDate' => $this->displayDateTime($row['RETURNED']), + ]; + } + return [ + 'count' => $totalCount, + 'transactions' => $historicLoans + ]; + } catch (PDOException $e) { + throw new ILSException($e->getMessage()); + } + } + /** * Get Patron Transactions * @@ -1825,4 +1921,70 @@ class KohaILSDI extends \VuFind\ILS\Driver\AbstractBase implements return null; } } + + /** + * Convert a database date to a displayable date. + * + * @param string $date Date to convert + * + * @return string + */ + public function displayDate($date) + { + if (empty($date)) { + return ""; + } elseif (preg_match("/^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d$/", $date) === 1) { + // YYYY-MM-DD HH:MM:SS + return $this->dateConverter->convertToDisplayDate('Y-m-d H:i:s', $date); + } elseif (preg_match("/^\d{4}-\d{2}-\d{2}$/", $date) === 1) { // YYYY-MM-DD + return $this->dateConverter->convertToDisplayDate('Y-m-d', $date); + } else { + error_log("Unexpected date format: $date"); + return $date; + } + } + + /** + * Convert a database datetime to a displayable date and time. + * + * @param string $date Datetime to convert + * + * @return string + */ + public function displayDateTime($date) + { + if (empty($date)) { + return ""; + } elseif (preg_match("/^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d$/", $date) === 1) { + // YYYY-MM-DD HH:MM:SS + return + $this->dateConverter->convertToDisplayDateAndTime( + 'Y-m-d H:i:s', $date + ); + } else { + error_log("Unexpected date format: $date"); + return $date; + } + } + + /** + * Helper method to determine whether or not a certain method can be + * called on this driver. Required method for any smart drivers. + * + * @param string $method The name of the called method. + * @param array $params Array of passed parameters + * + * @return bool True if the method can be called with the given parameters, + * false otherwise. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function supportsMethod($method, $params) + { + // Loan history is only available if properly configured + if ($method == 'getMyTransactionHistory') { + return !empty($this->config['TransactionHistory']['enabled']); + } + return is_callable([$this, $method]); + } } diff --git a/module/VuFind/src/VuFind/ILS/Driver/MultiBackend.php b/module/VuFind/src/VuFind/ILS/Driver/MultiBackend.php index 2a6c5705629..39f485421a4 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/MultiBackend.php +++ b/module/VuFind/src/VuFind/ILS/Driver/MultiBackend.php @@ -455,6 +455,30 @@ class MultiBackend extends AbstractBase implements \Zend\Log\LoggerAwareInterfac throw new ILSException('No suitable backend driver found'); } + /** + * Get Patron Transaction History + * + * This is responsible for retrieving all historic transactions + * (i.e. checked out items) by a specific patron. + * + * @param array $patron The patron array from patronLogin + * @param array $params Retrieval params + * + * @return array Array of the patron's transactions + */ + public function getMyTransactionHistory($patron, $params) + { + $source = $this->getSource($patron['cat_username']); + $driver = $this->getDriver($source); + if ($driver) { + $transactions = $driver->getMyTransactionHistory( + $this->stripIdPrefixes($patron, $source), $params + ); + return $this->addIdPrefixes($transactions, $source); + } + throw new ILSException('No suitable backend driver found'); + } + /** * Get Renew Details * diff --git a/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php b/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php index f8147c42293..051de58e90f 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php +++ b/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php @@ -617,6 +617,85 @@ class SierraRest extends AbstractBase implements TranslatorAwareInterface, return $finalResult; } + /** + * Get Patron Transaction History + * + * This is responsible for retrieving all historic transactions (i.e. checked + * out items) by a specific patron. + * + * @param array $patron The patron array from patronLogin + * @param array $params Parameters + * + * @throws DateException + * @throws ILSException + * @return array Array of the patron's historic transactions on success. + */ + public function getMyTransactionHistory($patron, $params) + { + $pageSize = isset($params['limit']) ? $params['limit'] : 50; + $offset = isset($params['page']) ? ($params['page'] - 1) * $pageSize : 0; + $sortOrder = isset($params['sort']) && 'checkout asc' === $params['sort'] + ? 'asc' : 'desc'; + $result = $this->makeRequest( + ['v3', 'patrons', $patron['id'], 'checkouts', 'history'], + [ + 'limit' => $pageSize, + 'offset' => $offset, + 'sortField' => 'outDate', + 'sortOrder' => $sortOrder, + 'fields' => 'item,outDate' + ], + 'GET', + $patron + ); + if (isset($result['code'])) { + return [ + 'success' => false, + 'status' => 146 === $result['code'] + ? 'ils_transaction_history_disabled' + : 'ils_connection_failed' + ]; + } + $transactions = []; + foreach ($result['entries'] as $entry) { + $transaction = [ + 'id' => '', + 'item_id' => $this->extractId($entry['item']), + 'checkoutDate' => $this->dateConverter->convertToDisplayDate( + 'Y-m-d', $entry['outDate'] + ) + ]; + // Fetch item information + $item = $this->makeRequest( + ['v3', 'items', $transaction['item_id']], + ['fields' => 'bibIds,varFields'], + 'GET', + $patron + ); + $transaction['volume'] = $this->extractVolume($item); + if (!empty($item['bibIds'])) { + $transaction['id'] = $item['bibIds'][0]; + + // Fetch bib information + $bib = $this->getBibRecord( + $transaction['id'], 'title,publishYear', $patron + ); + if (!empty($bib['title'])) { + $transaction['title'] = $bib['title']; + } + if (!empty($bib['publishYear'])) { + $transaction['publication_year'] = $bib['publishYear']; + } + } + $transactions[] = $transaction; + } + + return [ + 'count' => isset($result['total']) ? $result['total'] : 0, + 'transactions' => $transactions + ]; + } + /** * Get Patron Holds * @@ -1079,6 +1158,19 @@ class SierraRest extends AbstractBase implements TranslatorAwareInterface, */ public function getConfig($function, $params = null) { + if ('getMyTransactionHistory' === $function) { + if (empty($this->config['TransactionHistory']['enabled'])) { + return false; + } + return [ + 'max_results' => 100, + 'sort' => [ + 'checkout desc' => 'sort_checkout_date_desc', + 'checkout asc' => 'sort_checkout_date_asc' + ], + 'default_sort' => 'checkout desc' + ]; + } return isset($this->config[$function]) ? $this->config[$function] : false; } @@ -1097,10 +1189,14 @@ class SierraRest extends AbstractBase implements TranslatorAwareInterface, */ public function supportsMethod($method, $params) { - // Special case: change password is only available if properly configured. + // Changing password is only available if properly configured. if ($method == 'changePassword') { return isset($this->config['changePassword']); } + // Loan history is only available if properly configured + if ($method == 'getMyTransactionHistory') { + return !empty($this->config['TransactionHistory']['enabled']); + } return is_callable([$this, $method]); } diff --git a/themes/bootstrap3/templates/myresearch/controls/sort.phtml b/themes/bootstrap3/templates/myresearch/controls/sort.phtml new file mode 100644 index 00000000000..87cf4cb838e --- /dev/null +++ b/themes/bootstrap3/templates/myresearch/controls/sort.phtml @@ -0,0 +1,11 @@ +<div class="search-controls"> + <form class="search-sort" action="<?=$this->currentPath()?>" method="get" name="sort"> + <label for="sort_options_1"><?=$this->transEsc('Sort')?></label> + <select id="sort_options_1" name="sort" class="jumpMenu form-control"> + <? foreach ($this->sortList as $sortType => $sortData): ?> + <option value="<?=$this->escapeHtmlAttr($sortType)?>"<?=$sortData['selected']?' selected="selected"':''?>><?=$this->transEsc($sortData['desc'])?></option> + <? endforeach; ?> + </select> + <noscript><input type="submit" class="btn btn-default" value="<?=$this->transEsc("Set")?>" /></noscript> + </form> +</div> diff --git a/themes/bootstrap3/templates/myresearch/historicloans.phtml b/themes/bootstrap3/templates/myresearch/historicloans.phtml new file mode 100644 index 00000000000..a5a78967b72 --- /dev/null +++ b/themes/bootstrap3/templates/myresearch/historicloans.phtml @@ -0,0 +1,131 @@ +<? + // Set up page title: + $this->headTitle($this->translate('Loan History')); + + // Set up breadcrumbs: + $this->layout()->breadcrumbs = '<li><a href="' . $this->url('myresearch-home') . '">' . $this->transEsc('Your Account') . '</a></li> <li class="active">' . $this->transEsc('Loan History') . '</li>'; +?> + +<div class="<?=$this->layoutClass('mainbody')?>"> + <h2><?=$this->transEsc('Loan History')?></h2> + <?=$this->flashmessages()?> + + <?=$this->context($this)->renderInContext('librarycards/selectcard.phtml', ['user' => $this->auth()->isLoggedIn()]); ?> + + <? if (!empty($this->transactions)): ?> + <nav class="search-header hidden-print"> + <? if ($this->paginator): ?> + <div class="search-stats"> + <? + $end = min( + $this->paginator->getAbsoluteItemNumber($this->paginator->getItemCountPerPage()), + $this->paginator->getTotalItemCount() + ); + $transParams = [ + '%%start%%' => $this->localizedNumber($this->paginator->getAbsoluteItemNumber(1)), + '%%end%%' => $this->localizedNumber($end), + '%%total%%' => $this->localizedNumber($this->paginator->getTotalItemCount()) + ]; + ?> + <?=$this->translate('showing_items_of_html', $transParams); ?> + </div> + <? endif; ?> + <? if ($this->sortList): ?> + <?=$this->context($this)->renderInContext('myresearch/controls/sort.phtml', ['sortList' => $this->sortList]); ?> + <? endif; ?> + </nav> + + <? $i = 0; foreach ($this->transactions as $resource): ?> + <? $ilsDetails = $resource->getExtraDetail('ils_details'); ?> + <div id="record<?=$this->escapeHtmlAttr($resource->getUniqueId())?>" class="result"> + <? + $coverDetails = $this->record($resource)->getCoverDetails('checkedout', 'small', $this->recordLink()->getUrl($resource)); + $cover = $coverDetails['html']; + $thumbnail = false; + $thumbnailAlignment = $this->record($resource)->getThumbnailAlignment('account'); + if ($cover): + ob_start(); ?> + <div class="media-<?=$thumbnailAlignment ?> <?=$this->escapeHtmlAttr($coverDetails['size'])?>"> + <?=$cover ?> + </div> + <? $thumbnail = ob_get_contents(); + ob_end_clean(); + endif; ?> + <div class="media"> + <? if ($thumbnail && $thumbnailAlignment == 'left'): ?> + <?=$thumbnail ?> + <? endif ?> + <div class="media-body"> + <? + // If this is a non-missing Solr record, we should display a link: + if (is_a($resource, 'VuFind\\RecordDriver\\SolrDefault') && !is_a($resource, 'VuFind\\RecordDriver\\Missing')) { + $title = $resource->getTitle(); + $title = empty($title) ? $this->transEsc('Title not available') : $this->escapeHtml($title); + echo '<a href="' . $this->recordLink()->getUrl($resource) . + '" class="title">' . $title . '</a>'; + } elseif (isset($ilsDetails['title']) && !empty($ilsDetails['title'])){ + // If the record is not available in Solr, perhaps the ILS driver sent us a title we can show... + echo $this->escapeHtml($ilsDetails['title']); + } else { + // Last resort -- indicate that no title could be found. + echo $this->transEsc('Title not available'); + } + ?><br/> + <? $listAuthors = $resource->getPrimaryAuthors(); if (!empty($listAuthors)): ?> + <?=$this->transEsc('by')?>: + <a href="<?=$this->record($resource)->getLink('author', $listAuthors[0])?>"><?=$this->escapeHtml($listAuthors[0])?></a><? if (count($listAuthors) > 1): ?>, <?=$this->transEsc('more_authors_abbrev')?><? endif; ?><br/> + <? endif; ?> + <? if (count($resource->getFormats()) > 0): ?> + <?=$this->record($resource)->getFormatList() ?> + <br/> + <? endif; ?> + <? if (!empty($ilsDetails['volume'])): ?> + <strong><?=$this->transEsc('Volume')?>:</strong> <?=$this->escapeHtml($ilsDetails['volume'])?> + <br /> + <? endif; ?> + + <? if (!empty($ilsDetails['publication_year'])): ?> + <strong><?=$this->transEsc('Year of Publication')?>:</strong> <?=$this->escapeHtml($ilsDetails['publication_year'])?> + <br /> + <? endif; ?> + + <? if (!empty($ilsDetails['institution_name']) && (empty($ilsDetails['borrowingLocation']) || $ilsDetails['institution_name'] != $ilsDetails['borrowingLocation'])): ?> + <strong><?=$this->transEsc('location_' . $ilsDetails['institution_name'], [], $ilsDetails['institution_name'])?></strong> + <br /> + <? endif; ?> + + <? if (!empty($ilsDetails['borrowingLocation'])): ?> + <strong><?=$this->transEsc('Borrowing Location')?>:</strong> <?=$this->transEsc('location_' . $ilsDetails['borrowingLocation'], [], $ilsDetails['borrowingLocation'])?> + <br /> + <? endif; ?> + + <? if (!empty($ilsDetails['checkoutDate'])): ?> + <strong><?=$this->transEsc('Checkout Date')?>:</strong> <?=$this->escapeHtml($ilsDetails['checkoutDate'])?><? if (isset($ilsDetails['checkoutTime'])): ?> <span class="checkout-time"><?=$this->escapeHtml($ilsDetails['checkoutTime'])?><? endif; ?></span><br/> + <? endif; ?> + <? if (!empty($ilsDetails['returnDate'])): ?> + <strong><?=$this->transEsc('Return Date')?>:</strong> <?=$this->escapeHtml($ilsDetails['returnDate'])?><? if (isset($ilsDetails['returnTime'])): ?> <span class="return-time"><?=$this->escapeHtml($ilsDetails['returnTime'])?><? endif; ?></span><br/> + <? endif; ?> + <? if (!empty($ilsDetails['dueDate'])): ?> + <strong><?=$this->transEsc('Due Date')?>:</strong> <?=$this->escapeHtml($ilsDetails['dueDate'])?><? if (isset($ilsDetails['dueTime'])): ?> <span class="due-time"><?=$this->escapeHtml($ilsDetails['dueTime'])?></span><? endif; ?> + <? endif; ?> + + <? if (isset($ilsDetails['message']) && !empty($ilsDetails['message'])): ?> + <div class="alert alert-info"><?=$this->transEsc($ilsDetails['message'])?></div> + <? endif; ?> + </div> + <? if ($thumbnail && $thumbnailAlignment == 'right'): ?> + <?=$thumbnail ?> + <? endif ?> + </div> + <?=$resource->tryMethod('supportsCoinsOpenUrl')?'<span class="Z3988" title="' . $this->escapeHtmlAttr($resource->getCoinsOpenUrl()) . '"></span>':''?> + </div> + <? endforeach; ?> + <?=$this->paginator ? $this->paginationControl($this->paginator, 'Sliding', 'Helpers/pagination.phtml', ['params' => $this->params]) : ''?> + <? else: ?> + <?=$this->transEsc('loan_history_empty')?> + <? endif; ?> +</div> + +<div class="<?=$this->layoutClass('sidebar')?>"> + <?=$this->context($this)->renderInContext("myresearch/menu.phtml", ['active' => 'historicloans'])?> +</div> diff --git a/themes/bootstrap3/templates/myresearch/menu.phtml b/themes/bootstrap3/templates/myresearch/menu.phtml index 7dc68d9957f..4c1de81a218 100644 --- a/themes/bootstrap3/templates/myresearch/menu.phtml +++ b/themes/bootstrap3/templates/myresearch/menu.phtml @@ -16,6 +16,11 @@ <i class="fa fa-fw fa-book" aria-hidden="true"></i> <?=$this->transEsc('Checked Out Items')?> </a> <? endif; ?> + <? if ($this->ils()->checkFunction('getMyTransactionHistory', $capabilityParams)): ?> + <a href="<?=$this->url('myresearch-historicloans')?>"<?=$this->active == 'historicloans' ? ' class="active"' : ''?>"> + <i class="fa fa-fw fa-history" aria-hidden="true"></i> <?=$this->transEsc('Loan History')?> + </a> + <? endif; ?> <? if ($this->ils()->checkCapability('getMyHolds', $capabilityParams)): ?> <a href="<?=$this->url('myresearch-holds')?>"<?=$this->active == 'holds' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-flag" aria-hidden="true"></i> <?=$this->transEsc('Holds and Recalls')?> -- GitLab