diff --git a/config/vufind/Alma.ini b/config/vufind/Alma.ini index 3b2017ad18ab6a19d28d83b165b428ac8f6d0966..f5bda3647c7df1221eeef3f95d08075b6b4f1bea 100644 --- a/config/vufind/Alma.ini +++ b/config/vufind/Alma.ini @@ -87,3 +87,10 @@ purgeDate = ; The webhook secret. This must be the same value that was added to the Alma webhook configuration as a secret. secret = YOUR_WEBHOOK_SECRET_FROM_ALMA +[Holdings] +; The digital delivery URL for your Alma instance. Replace at least SOMETHING and +; INSTITUTION with correct values. +;digitalDeliveryUrl = "https://SOMETHING.alma.exlibrisgroup.com/view/delivery/INSTITUTION/%%id%%" +; Inventory types to display from Alma. A colon-separated list. Supported values +; are "physical", "electronic" and "digital". By default all are displayed. +;inventoryTypes = "physical:electronic" diff --git a/module/VuFind/src/VuFind/ILS/Connection.php b/module/VuFind/src/VuFind/ILS/Connection.php index 6b598e6f7437dadb48f4ff36755a9e71a3eae57a..702baa849a89999871462695effcf07d3064832e 100644 --- a/module/VuFind/src/VuFind/ILS/Connection.php +++ b/module/VuFind/src/VuFind/ILS/Connection.php @@ -1016,6 +1016,7 @@ class Connection implements TranslatorAwareInterface, LoggerAwareInterface return [ 'total' => $holdings['total'] ?? count($holdings), 'holdings' => $holdings['holdings'] ?? $holdings, + 'electronic_holdings' => $holdings['electronic_holdings'] ?? [], 'page' => $finalOptions['page'], 'itemLimit' => $finalOptions['itemLimit'], ]; diff --git a/module/VuFind/src/VuFind/ILS/Driver/Alma.php b/module/VuFind/src/VuFind/ILS/Driver/Alma.php index 82791112b48cafcc6ece66e1adbd30b5475b4f1e..6c7ce16898907a95aab31c81d269ee56e1e972af 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Alma.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Alma.php @@ -387,6 +387,24 @@ class Alma extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterface } } + // Fetch also digital and/or electronic inventory if configured + $types = $this->getInventoryTypes(); + if (in_array('d_avail', $types) || in_array('e_avail', $types)) { + // No need for physical items + $key = array_search('p_avail', $types); + if (false !== $key) { + unset($types[$key]); + } + $statuses = $this->getStatusesForInventoryTypes((array)$id, $types); + $electronic = []; + foreach ($statuses as $record) { + foreach ($record as $status) { + $electronic[] = $status; + } + } + $results['electronic_holdings'] = $electronic; + } + return $results; } @@ -1202,60 +1220,7 @@ class Alma extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterface */ public function getStatuses($ids) { - $results = []; - $params = [ - 'mms_id' => implode(',', $ids), - 'expand' => 'p_avail,e_avail,d_avail' - ]; - if ($bibs = $this->makeRequest('/bibs', $params)) { - foreach ($bibs as $bib) { - $marc = new \File_MARCXML( - $bib->record->asXML(), - \File_MARCXML::SOURCE_STRING - ); - $status = []; - $tmpl = [ - 'id' => (string)$bib->mms_id, - 'source' => 'Solr', - 'callnumber' => isset($bib->isbn) - ? (string)$bib->isbn - : '' - ]; - if ($record = $marc->next()) { - // Physical - $physicalItems = $record->getFields('AVA'); - foreach ($physicalItems as $field) { - $avail = $field->getSubfield('e')->getData(); - $item = $tmpl; - $item['availability'] = strtolower($avail) === 'available'; - $item['location'] = (string)$field->getSubfield('c') - ->getData(); - $status[] = $item; - } - // Electronic - $electronicItems = $record->getFields('AVE'); - foreach ($electronicItems as $field) { - $avail = $field->getSubfield('e')->getData(); - $item = $tmpl; - $item['availability'] = strtolower($avail) === 'available'; - $status[] = $item; - } - // Digital - $digitalItems = $record->getFields('AVD'); - foreach ($digitalItems as $field) { - $avail = $field->getSubfield('e')->getData(); - $item = $tmpl; - $item['availability'] = strtolower($avail) === 'available'; - $status[] = $item; - } - } else { - // TODO: Throw error - error_log('no record'); - } - $results[] = $status; - } - } - return $results; + return $this->getStatusesForInventoryTypes($ids, $this->getInventoryTypes()); } /** @@ -1486,8 +1451,9 @@ class Alma extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterface $xml = $this->makeRequest('/courses/' . $courseID . '/reading-lists'); $reserves = []; foreach ($xml as $list) { + $listId = $list->id; $listXML = $this->makeRequest( - "/courses/${$courseID}/reading-lists/${$list->id}/citations" + "/courses/${$courseID}/reading-lists/${$listId}/citations" ); foreach ($listXML as $citation) { $reserves[$citation->id] = $citation->metadata; @@ -1549,6 +1515,129 @@ class Alma extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterface } } + /** + * Get the inventory types to be displayed. Possible values are: + * p_avail,e_avail,d_avail + * + * @return array + */ + protected function getInventoryTypes() + { + $types = explode( + ':', + $this->config['Holdings']['inventoryTypes'] + ?? 'physical:digital:electronic' + ); + + $result = []; + $map = [ + 'physical' => 'p_avail', + 'digital' => 'd_avail', + 'electronic' => 'e_avail' + ]; + $types = array_flip($types); + foreach ($map as $src => $dest) { + if (isset($types[$src])) { + $result[] = $dest; + } + } + + return $result; + } + + /** + * Get Statuses for inventory types + * + * This is responsible for retrieving the status information for a + * collection of records with specified inventory types. + * + * @param array $ids The array of record ids to retrieve the status for + * @param array $types Inventory types + * + * @return array An array of getStatus() return values on success. + */ + protected function getStatusesForInventoryTypes($ids, $types) + { + $results = []; + $params = [ + 'mms_id' => implode(',', $ids), + 'expand' => implode(',', $types) + ]; + if ($bibs = $this->makeRequest('/bibs', $params)) { + foreach ($bibs as $bib) { + $marc = new \File_MARCXML( + $bib->record->asXML(), + \File_MARCXML::SOURCE_STRING + ); + $status = []; + $tmpl = [ + 'id' => (string)$bib->mms_id, + 'source' => 'Solr', + 'callnumber' => isset($bib->isbn) + ? (string)$bib->isbn + : '' + ]; + if ($record = $marc->next()) { + // Physical + $physicalItems = $record->getFields('AVA'); + foreach ($physicalItems as $field) { + $avail = $field->getSubfield('e')->getData(); + $item = $tmpl; + $item['availability'] = strtolower($avail) === 'available'; + $item['location'] = (string)$field->getSubfield('c') + ->getData(); + $status[] = $item; + } + // Electronic + $electronicItems = $record->getFields('AVE'); + foreach ($electronicItems as $field) { + $avail = $field->getSubfield('e')->getData(); + $item = $tmpl; + $item['availability'] = strtolower($avail) === 'available'; + $item['location'] = $field->getSubfield('m')->getData(); + $item['location'] = $field->getSubfield('m')->getData(); + $url = $field->getSubfield('u')->getData(); + if (preg_match('/^https?:\/\//', $url)) { + $item['locationhref'] = $url; + } + $item['status'] = $field->getSubfield('s')->getData(); + $status[] = $item; + } + // Digital + $deliveryUrl + = $this->config['Holdings']['digitalDeliveryUrl'] ?? ''; + $digitalItems = $record->getFields('AVD'); + if ($digitalItems && !$deliveryUrl) { + $this->logWarning( + 'Digital items exist for ' . (string)$bib->mms_id + . ', but digitalDeliveryUrl not set -- unable to' + . ' generate links' + ); + } + foreach ($digitalItems as $field) { + $item = $tmpl; + unset($item['callnumber']); + $item['availability'] = true; + $item['location'] = $field->getSubfield('e')->getData(); + if ($deliveryUrl) { + $item['locationhref'] = str_replace( + '%%id%%', + $field->getSubfield('b')->getData(), + $deliveryUrl + ); + } + $status[] = $item; + } + } else { + // TODO: Throw error + error_log('no record'); + } + $results[(string)$bib->mms_id] = $status; + } + } + return $results; + } + // @codingStandardsIgnoreStart /** diff --git a/module/VuFind/src/VuFind/ILS/Driver/Demo.php b/module/VuFind/src/VuFind/ILS/Driver/Demo.php index 0aede2d63aea41ba5ca36514e47861726e7bb660..af9f7b18b5da2bf2a57d097100ec71891f60f596 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Demo.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Demo.php @@ -376,7 +376,7 @@ class Demo extends AbstractBase $status = $this->getFakeStatus(); $location = $this->getFakeLoc(); $locationhref = ($location === 'Campus A') ? 'http://campus-a' : false; - return [ + $result = [ 'id' => $id, 'source' => $this->getRecordSource(), 'item_id' => $number, @@ -396,8 +396,26 @@ class Demo extends AbstractBase 'addStorageRetrievalRequestLink' => $patron ? 'check' : false, 'ILLRequest' => 'auto', 'addILLRequestLink' => $patron ? 'check' : false, - 'services' => $status == 'Available' ? $this->getFakeServices() : [] + 'services' => $status == 'Available' ? $this->getFakeServices() : [], ]; + + switch (rand(1, 5)) { + case 1: + $result['location'] = 'Digital copy available'; + $result['locationhref'] = 'http://digital'; + $result['__electronic__'] = true; + $result['availability'] = true; + $result['status'] = ''; + break; + case 2: + $result['location'] = 'Electronic Journals'; + $result['locationhref'] = 'http://electronic'; + $result['__electronic__'] = true; + $result['availability'] = true; + $result['status'] = 'Available from ' . rand(2010, 2019); + } + + return $result; } /** @@ -687,6 +705,14 @@ class Demo extends AbstractBase $status[$i]['enumchron'] = "volume $volume, issue $seriesIssue"; } + // Filter out electronic holdings from the normal holdings list: + $status = array_filter( + $status, + function ($a) { + return !($a['__electronic__'] ?? false); + } + ); + // Slice out a chunk if pagination is enabled. $slice = null; if ($options['itemLimit'] ?? null) { @@ -702,10 +728,22 @@ class Demo extends AbstractBase ); } + // Electronic holdings: + $statuses = $this->getStatus($id); + $electronic = []; + foreach ($statuses as $item) { + if ($item['__electronic__'] ?? false) { + // Don't expose internal __electronic__ flag upstream: + unset($item['__electronic__']); + $electronic[] = $item; + } + } + // Send back final value: return [ 'total' => count($status), 'holdings' => $slice ?: $status, + 'electronic_holdings' => $electronic ]; } diff --git a/module/VuFind/src/VuFind/ILS/Logic/Holds.php b/module/VuFind/src/VuFind/ILS/Logic/Holds.php index e511ca4ea4183a8011357c5e362870b2d496f951..54f4524a8adc61c9b4cca1529f25ce9e0d160a07 100644 --- a/module/VuFind/src/VuFind/ILS/Logic/Holds.php +++ b/module/VuFind/src/VuFind/ILS/Logic/Holds.php @@ -234,7 +234,8 @@ class Holds 'total' => $result['total'], 'page' => $result['page'], 'itemLimit' => $result['itemLimit'], - 'holdings' => $this->formatHoldings($holdings) + 'holdings' => $this->formatHoldings($holdings), + 'electronic_holdings' => $result['electronic_holdings'] ?? [], ]; } diff --git a/themes/bootstrap3/templates/RecordTab/holdingsils.phtml b/themes/bootstrap3/templates/RecordTab/holdingsils.phtml index 1fe34f0872ce5e4e7a24825e61a1bdcf4cfc4d72..b116e80731e73a742b065973237bcb85642a8497 100644 --- a/themes/bootstrap3/templates/RecordTab/holdingsils.phtml +++ b/themes/bootstrap3/templates/RecordTab/holdingsils.phtml @@ -12,7 +12,13 @@ try { $holdings = $this->driver->getRealTimeHoldings(); } catch (\VuFind\Exception\ILS $e) { - $holdings = ['holdings' => []]; + $holdings = [ + 'holdings' => [], + 'electronic_holdings' => [], + 'total' => 0, + 'page' => 0, + 'itemLimit' => 0 + ]; $offlineMode = 'ils-offline'; } // Set page title. @@ -28,6 +34,7 @@ <?php endif; ?> <?=($offlineMode == "ils-offline") ? $this->render('Helpers/ils-offline.phtml', ['offlineModeMsg' => 'ils_offline_holdings_message']) : ''?> + <?php if (($this->ils()->getHoldsMode() == 'driver' && !empty($holdings['holdings'])) || $this->ils()->getTitleHoldsMode() == 'driver'): ?> <?php if ($account->loginEnabled() && $offlineMode != 'ils-offline'): ?> <?php if (!$user): ?> @@ -54,6 +61,14 @@ <?php if ($openUrlActive): ?><?=$openUrl->renderTemplate()?><?php endif; ?> <?php if ($doiActive): ?><?=$doi->renderTemplate()?><?php endif; ?> <?php endif; ?> + +<?php if (!empty($holdings['electronic_holdings'])): ?> + <?=$this->context($this)->renderInContext( + 'RecordTab/holdingsils/electronic.phtml', + ['holdings' => $holdings['electronic_holdings']] + );?> +<?php endif; ?> + <?php foreach ($holdings['holdings'] ?? [] as $holding): ?> <h3> <?php $locationText = $this->transEsc('location_' . $holding['location'], [], $holding['location']); ?> diff --git a/themes/bootstrap3/templates/RecordTab/holdingsils/electronic.phtml b/themes/bootstrap3/templates/RecordTab/holdingsils/electronic.phtml new file mode 100644 index 0000000000000000000000000000000000000000..aba2d793985735d4ba0364a0245f3caabfe30e18 --- /dev/null +++ b/themes/bootstrap3/templates/RecordTab/holdingsils/electronic.phtml @@ -0,0 +1,24 @@ +<h3><?=$this->transEsc('Electronic')?></h3> +<table class="table table-striped"> + <?php foreach ($this->holdings as $item): ?> + <tr> + <td class="fullLocation"> + <?php $locationText = $this->transEsc('location_' . $item['location'], [], $item['location']); ?> + <?php if ($item['locationhref'] ?? false): ?> + <a href="<?=$item['locationhref']?>" target="_blank"><?=$locationText?></a> + <?php else: ?> + <?=$locationText?> + <?php endif; ?> + </td> + <td class="fullAvailability"> + <?php if ($item['use_unknown_message'] ?? false): ?> + <span><?=$this->transEsc('status_unknown_message')?></span> + <?php elseif ($item['availability']): ?> + <span class="text-success"><?=!empty($item['status']) ? $this->transEsc($item['status']) : $this->transEsc('Available')?></span> + <?php else: ?> + <span class="text-danger"><?=$this->transEsc($item['status'])?></span> + <?php endif; ?> + </td> + </tr> + <?php endforeach; ?> +</table>