Skip to content

Commit

Permalink
Display status of item locations by status priority. (#3758)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrothTobias authored Nov 7, 2024
1 parent ce0a1f6 commit f42dbcd
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 179 deletions.
139 changes: 69 additions & 70 deletions module/VuFind/src/VuFind/AjaxHandler/GetItemStatuses.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@
use VuFind\ILS\Logic\Holds;
use VuFind\Session\Settings as SessionSettings;

use function array_map;
use function array_unique;
use function count;
use function in_array;
use function is_array;
use function is_string;

/**
* "Get Item Status" AJAX handler
Expand Down Expand Up @@ -127,7 +130,8 @@ protected function translateList($transPrefix, $list)
{
$transList = [];
foreach ($list as $current) {
$transList[] = $this->translateWithPrefix($transPrefix, $current);
// $current can be an array if pickValue() is called with callnumbers
$transList[] = is_string($current) ? $this->translateWithPrefix($transPrefix, $current) : $current;
}
return $transList;
}
Expand All @@ -139,37 +143,27 @@ protected function translateList($transPrefix, $list)
* @param array $rawList Array of values to choose from.
* @param string $mode config.ini setting -- first, all or msg
* @param string $msg Message to display if $mode == "msg"
* @param string $transPrefix Translator prefix to apply to values (false to
* omit translation of values)
* @param string $transPrefix Translator prefix to apply to values (false to omit translation of values)
*
* @return string
* @return array
*/
protected function pickValue($rawList, $mode, $msg, $transPrefix = false)
{
// Make sure array contains only unique values:
$list = array_unique($rawList);

// If there is only one value in the list, or if we're in "first" mode,
// send back the first list value:
if ($mode == 'first' || count($list) == 1) {
if ($transPrefix) {
return $this->translateWithPrefix($transPrefix, $list[0]);
}
return $list[0];
} elseif (count($list) == 0) {
// Empty list? Return a blank string:
return '';
} elseif ($mode == 'all') {
// All values mode? Return comma-separated values:
return implode(
",\t",
$transPrefix ? $this->translateList($transPrefix, $list) : $list
);
} else {
// array unique for multidimensional arrays due to callnumber array,
// can be slow for larger/more complex arrays
$list = array_map('unserialize', array_unique(array_map('serialize', $rawList)));

// If we're in "first" mode, reduce list to first list value:
if ($mode == 'first' && count($list) > 0) {
$list = [$list[0]];
} elseif ($mode == 'msg' && count($list) > 1) {
// Message mode? Return the specified message, translated to the
// appropriate language.
return $this->translate($msg);
return [$this->translate($msg)];
}

return $transPrefix ? $this->translateList($transPrefix, $list) : $list;
}

/**
Expand Down Expand Up @@ -219,17 +213,47 @@ protected function reduceServices(array $rawServices)
}

/**
* Create a delimited version of the call number to allow the Javascript code
* to handle the prefix appropriately.
* Create an array with the callnumber and prefix of the given item.
*
* @param array $item Item's holding data.
*
* @return array Associative array with the keys 'prefix' and 'callnumber'
*/
protected function getCallNumberArray(array $item): array
{
return [
'prefix' => $item['callnumber_prefix'] ?? '',
'callnumber' => $item['callnumber'],
];
}

/**
* Render the callnumber HTML.
*
* @param string $prefix Callnumber prefix or empty string.
* @param string $callnumber Main call number.
* @param string $callnumberSetting The callnumber mode setting
* @param array $callnumbers Callnumbers to render
*
* @return string
*/
protected function formatCallNo($prefix, $callnumber)
protected function renderCallnumbers(string $callnumberSetting, array $callnumbers): string
{
return !empty($prefix) ? $prefix . '::::' . $callnumber : $callnumber;
$html = [];

$callnumberHandler = $this->getCallnumberHandler($callnumbers, $callnumberSetting);
foreach ($callnumbers as $number) {
$displayCallnumber = $actualCallnumber = $number['callnumber'];

if (!empty($number['prefix'])) {
$displayCallnumber = $number['prefix'] . ' ' . $displayCallnumber;
}

$html[] = $this->renderer->render(
'ajax/itemCallnumber',
compact('actualCallnumber', 'displayCallnumber', 'callnumberHandler')
);
}

return implode(",\t", $html);
}

/**
Expand All @@ -255,10 +279,7 @@ protected function getItemStatus(
$services = [];
foreach ($record as $info) {
// Store call number/location info:
$callNumbers[] = $this->formatCallNo(
$info['callnumber_prefix'] ?? '',
$info['callnumber']
);
$callNumbers[] = $this->getCallNumberArray($info);

$locations[] = $info['location'];
// Store all available services
Expand All @@ -267,11 +288,6 @@ protected function getItemStatus(
}
}

$callnumberHandler = $this->getCallnumberHandler(
$callNumbers,
$callnumberSetting
);

// Determine call number string based on findings:
$callNumber = $this->pickValue(
$callNumbers,
Expand Down Expand Up @@ -304,13 +320,12 @@ protected function getItemStatus(
'id' => $record[0]['id'],
'availability' => $combinedAvailability->availabilityAsString(),
'availability_message' => $availabilityMessage,
'location' => htmlentities($location, ENT_COMPAT, 'UTF-8'),
'location' => htmlentities(implode(",\t", $location), ENT_COMPAT, 'UTF-8'),
'locationList' => false,
'reserve' => $reserve ? 'true' : 'false',
'reserve_message'
=> $this->translate($reserve ? 'on_reserve' : 'Not On Reserve'),
'callnumber' => htmlentities($callNumber, ENT_COMPAT, 'UTF-8'),
'callnumber_handler' => $callnumberHandler,
'callnumberHtml' => $this->renderCallnumbers($callnumberSetting, $callNumber),
];
}

Expand All @@ -330,49 +345,33 @@ protected function getItemStatusGroup($record, $callnumberSetting)
// Summarize call number, location and availability info across all items:
$locations = [];
foreach ($record as $info) {
$availabilityStatus = $info['availability'];
// Find an available copy
if ($availabilityStatus->isAvailable()) {
if ('true' !== ($locations[$info['location']]['available'] ?? null)) {
$locations[$info['location']]['available'] = $availabilityStatus->getStatusDescription();
}
}
// Check for a use_unknown_message flag
if ($availabilityStatus->is(AvailabilityStatusInterface::STATUS_UNKNOWN)) {
$locations[$info['location']]['status_unknown'] = true;
}
// Store call number/location info:
$locations[$info['location']]['callnumbers'][] = $this->formatCallNo(
$info['callnumber_prefix'] ?? '',
$info['callnumber']
);
$locations[$info['location']]['callnumbers'][] = $this->getCallNumberArray($info);
$locations[$info['location']]['items'][] = $info;
}

// Build list split out by location:
$locationList = [];
foreach ($locations as $location => $details) {
$locationCallnumbers = array_unique($details['callnumbers']);
// Determine call number string based on findings:
$callnumberHandler = $this->getCallnumberHandler(
$locationCallnumbers,
$callnumberSetting
);
$locationCallnumbers = $this->pickValue(
$locationCallnumbers,
$details['callnumbers'],
$callnumberSetting,
'Multiple Call Numbers'
);

// Get combined availability for location
$locationStatus = $this->availabilityStatusManager->combine($details['items']);

$locationInfo = [
'availability' => $details['available'] ?? false,
'availability' => $locationStatus['availability'],
'location' => htmlentities(
$this->translateWithPrefix('location_', $location),
ENT_COMPAT,
'UTF-8'
),
'callnumbers' =>
htmlentities($locationCallnumbers, ENT_COMPAT, 'UTF-8'),
'status_unknown' => $details['status_unknown'] ?? false,
'callnumber_handler' => $callnumberHandler,
'callnumberHtml' =>
$this->renderCallnumbers($callnumberSetting, $locationCallnumbers),
];
$locationList[] = $locationInfo;
}
Expand All @@ -389,11 +388,11 @@ protected function getItemStatusGroup($record, $callnumberSetting)
'availability' => $combinedAvailability->availabilityAsString(),
'availability_message' => $this->getAvailabilityMessage($combinedAvailability),
'location' => false,
'locationList' => $locationList,
'locationList' => $this->renderer->render('ajax/itemLocationList', ['locationList' => $locationList]),
'reserve' => $reserve ? 'true' : 'false',
'reserve_message'
=> $this->translate($reserve ? 'on_reserve' : 'Not On Reserve'),
'callnumber' => false,
'callnumberHtml' => false,
];
}

Expand Down
4 changes: 2 additions & 2 deletions module/VuFind/src/VuFind/ILS/Logic/AvailabilityStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ public function availabilityAsString(): string
{
switch ($this->availability) {
case AvailabilityStatusInterface::STATUS_UNAVAILABLE:
return 'false';
return 'unavailable';
case AvailabilityStatusInterface::STATUS_AVAILABLE:
return 'true';
return 'available';
case AvailabilityStatusInterface::STATUS_UNKNOWN:
return 'unknown';
default:
Expand Down
49 changes: 49 additions & 0 deletions module/VuFind/src/VuFind/View/Helper/Root/AvailabilityStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,34 @@ class AvailabilityStatus extends \Laminas\View\Helper\AbstractHelper
*/
protected string $classUnknown = 'text-muted';

/**
* Icon for available items.
*
* @var string
*/
protected string $iconAvailable = 'status-available';

/**
* Icon for unavailable items.
*
* @var string
*/
protected string $iconUnavailable = 'status-unavailable';

/**
* Icon for items where status is uncertain.
*
* @var string
*/
protected string $iconUncertain = 'status-uncertain';

/**
* Icon for items where status is unknown.
*
* @var string
*/
protected string $iconUnknown = 'status-unknown';

/**
* Message cache
*
Expand Down Expand Up @@ -98,6 +126,27 @@ public function getClass(AvailabilityStatusInterface $availabilityStatus): strin
return $this->classUncertain;
}

/**
* Get icon name for availability status.
*
* @param AvailabilityStatusInterface $availabilityStatus Availability Status
*
* @return string
*/
public function getIcon(AvailabilityStatusInterface $availabilityStatus): string
{
if ($availabilityStatus->is(\VuFind\ILS\Logic\AvailabilityStatusInterface::STATUS_UNAVAILABLE)) {
return $this->iconUnavailable;
}
if ($availabilityStatus->is(\VuFind\ILS\Logic\AvailabilityStatusInterface::STATUS_AVAILABLE)) {
return $this->iconAvailable;
}
if ($availabilityStatus->is(\VuFind\ILS\Logic\AvailabilityStatusInterface::STATUS_UNKNOWN)) {
return $this->iconUnknown;
}
return $this->iconUncertain;
}

/**
* Render ajax status.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ public function testItemStatus(
);
$this->assertEquals(
'Main Library',
$this->findCssAndGetText($page, '.result-body .callnumAndLocation .groupLocation .text-success')
$this->findCssAndGetText(
$page,
'.result-body .callnumAndLocation .groupLocation .text-' . $expectedType
)
);
}
} else {
Expand All @@ -196,8 +199,8 @@ public function testItemStatus(
} else {
// No extra items to care for:
if ('group' === $multipleLocations) {
// Unknown status displays as warning:
$type = null === $availability ? 'warning' : 'danger';
// Unknown status displays as muted:
$type = null === $availability ? 'muted' : 'danger';
$selector = ".result-body .callnumAndLocation .groupLocation .text-$type";
} else {
$selector = '.result-body .callnumAndLocation .location';
Expand Down
Loading

0 comments on commit f42dbcd

Please sign in to comment.