diff --git a/local/config/vufind/searches.ini b/local/config/vufind/searches.ini index 1dbec8eddd33723afa4ba2786ee92204f0ada6a1..82440759a23ef798b0fbd54f85784c6f4c0e66d8 100644 --- a/local/config/vufind/searches.ini +++ b/local/config/vufind/searches.ini @@ -98,6 +98,15 @@ retain_filters_by_default = true ;default_filters[] = "institution:MyInstitution" ;default_filters[] = "(format:Book AND institution:MyInstitution)" +; Avoid query rebuilding in case the user injects a query in lucene like syntax. +; Provide an array of Search Types for which the lucene query syntax detection +; MUST NOT be applied. +; Note: this does not work for Search Types used in advanced search as avoiding +; advanced lucene detection needs would have to be set either for all +; Search Types used in advanced search or for none at all. Disabling +; advanced lucene detection for a search does not work partially! +;override_advanced_lucene_detection[] = Signatur; + [Cache] ; This controls whether the parsed searchspecs.yaml file will be stored to ; improve search performance; legal options are APC (use APC cache), File (store diff --git a/module/fid/src/View/Helper/Root/GetIt.php b/module/fid/src/View/Helper/Root/GetIt.php index b8a79995a376bc6b04a5eeb4374e6094fe6f0e27..f12571cd82d20455ef0ab824bcd1c3dc48a94b5c 100644 --- a/module/fid/src/View/Helper/Root/GetIt.php +++ b/module/fid/src/View/Helper/Root/GetIt.php @@ -241,7 +241,6 @@ class GetIt extends AbstractHelper $accordeonHeadline = $this->accordeonHeadlineDefault; $boxHeadline = $this->translate('Get it'); $notice = $this->translate('getit_text_default'); - $noticeWithLink = null; $showLinks = true; $showOrderButton = false; $showPartCopyButton = false; @@ -250,6 +249,7 @@ class GetIt extends AbstractHelper $isEBCEBooks = false; $hideNotice = false; $isAiSidRecord = false; + $noticeLinkType = 'register'; // let specific functions override defaults where necessary foreach ($this->sids as $sid_config) { diff --git a/module/finc/config/module.config.php b/module/finc/config/module.config.php index b2f39e304cdf7b8452a8434d147891356501df92..7673bcd88c9a4de514c7e6cf031600f08fd18083 100644 --- a/module/finc/config/module.config.php +++ b/module/finc/config/module.config.php @@ -164,6 +164,7 @@ $config = [ ], 'finc\RecordDriver\SolrMarcFinc' => [ 'VuFind\RecordDriver\IlsAwareDelegatorFactory', + 'finc\RecordDriver\SolrMarcUrlRulesDelegatorFactory', ], 'finc\RecordDriver\SolrMarcFincPDA' => [ 'VuFind\RecordDriver\IlsAwareDelegatorFactory', diff --git a/module/finc/src/finc/Controller/Admin/I18nController.php b/module/finc/src/finc/Controller/Admin/I18nController.php index 107bde55196cb7ac9b76c5294e48c2ad6ac0c079..7052a038b9d04e14d16ef0be2e2c8b4e4e37eafd 100644 --- a/module/finc/src/finc/Controller/Admin/I18nController.php +++ b/module/finc/src/finc/Controller/Admin/I18nController.php @@ -20,18 +20,17 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ - namespace finc\Controller\Admin; use Symfony\Component\Filesystem\Filesystem as Fs; use VuFind\Auth\InvalidArgumentException; use VuFind\Config\Writer as ConfigWriter; -use VuFind\Controller\AbstractBase; use VuFind\I18n\Translator\Loader\ExtendedIni; +use VuFindAdmin\Controller\AbstractAdmin; use Zend\I18n\Translator\Translator; use Zend\ServiceManager\ServiceLocatorInterface; -class I18nController extends AbstractBase +class I18nController extends AbstractAdmin { /** * @var string[] @@ -89,7 +88,6 @@ class I18nController extends AbstractBase $this->languages = array_combine($locales, $languages); } - public function homeAction() { $params = $this->params(); @@ -165,7 +163,6 @@ class I18nController extends AbstractBase $locale, $reset ? null : $value ); - } protected function getTranslations(): array @@ -243,14 +240,13 @@ class I18nController extends AbstractBase protected function saveTranslation($domain, $token, $locale, $value) { $fs = new FS(); - list($filename) = array_slice($this->dirs, -1);; + list($filename) = array_slice($this->dirs, -1); if ($domain !== 'default') { $filename .= "/$domain"; } $filename .= "/$locale.ini"; try { - if (empty($token)) { throw new InvalidArgumentException( $this->translate('This field is required') . ": Token" @@ -366,4 +362,4 @@ class I18nController extends AbstractBase $response->setStatusCode(205); return $response; } -} \ No newline at end of file +} diff --git a/module/finc/src/finc/Controller/CustomTraits/SearchTrait.php b/module/finc/src/finc/Controller/CustomTraits/SearchTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..6b9fe2396111f7c735e698e1f97aabc85317f91d --- /dev/null +++ b/module/finc/src/finc/Controller/CustomTraits/SearchTrait.php @@ -0,0 +1,100 @@ +<?php +/** + * Search Trait + * + * PHP version 7 + * + * Copyright (C) Villanova University 2010. + * Copyright (C) Leipzig University Library 2021. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Controller + * @author Demian Katz <demian.katz@villanova.edu> + * @author Robert Lange <lange@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +namespace finc\Controller\CustomTraits; + +use VuFindSearch\Query\Query as Query; + +/** + * Search Trait + * + * @category VuFind + * @package Controller + * @author Demian Katz <demian.katz@villanova.edu> + * @author Robert Lange <lange@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +trait SearchTrait +{ + /** + * Search for related records specified via field and values. Current Record may + * be explicity excluded from the search. + * + * Note: Location of trait is not optimal but a more appropriate location + * in the search backend seemed too complicated for implementation, + * therefore controller traits was chosen. + * TODO: find more appropriate location for trait + * + * Returns an array containing + * * 'first_results' - array of up to $limit record objects + * * 'more_query' - query string to be used when tryiong to retrieve ALL + * related records + * + * @param string $field solr field + * @param array $values solr values to look for + * @param int $limit max results (default is 20) + * @param array $filters fq associative array $filterField => $value + * @param string $backend_id type of backend (default is Solr) + * @param bool $excludeUniqueId when THIS current record should be excluded TRUE, otherwise FALSE (default is true) + * + * @return mixed + */ + public function searchRelatedRecords( + $field, + $values, + $limit = 20, + $filters = [], + $backend_id = 'Solr', + $excludeUniqueId = true + ) { + if (!empty($filters)) { + $fq = ''; + foreach ($filters as $filterField => $value) { + $fq = (empty($fq) ? '' : ' AND ') + . "$filterField:$value"; + } + } + + $query = new Query( + $field . ':(' . implode(' OR ', $values) + . ')' . ($excludeUniqueId ? ' AND NOT id:' . $this->getUniqueID() : '') + . (isset($fq) ? " AND $fq" : '') + ); + + $result = $this->searchService->search($backend_id, $query, 0, $limit); + $return['first_results'] = $result->getRecords(); + + if (isset($limit) && $result->getTotal() > $limit) { + $return['more_query'] = $query->getString(); + } + + return $return; + } +} diff --git a/module/finc/src/finc/ILS/Driver/FincILS.php b/module/finc/src/finc/ILS/Driver/FincILS.php index edf613808c1d286beb5a95bfe5832a146bdc1ca4..fd6dee3b7a9956b03b1e90bff2d47e4b69205f09 100644 --- a/module/finc/src/finc/ILS/Driver/FincILS.php +++ b/module/finc/src/finc/ILS/Driver/FincILS.php @@ -30,9 +30,11 @@ namespace finc\ILS\Driver; use DateTime; use DateInterval; use DateTimeZone; +use finc\Controller\CustomTraits\SearchTrait as SearchTrait; use Herrera\Json\Exception\Exception; use Sabre\VObject; use VuFind\Exception\ILS as ILSException; +use VuFindSearch\ParamBag; use VuFindSearch\Query\Query; use VuFindSearch\Service as SearchService; use Zend\Log\LoggerAwareInterface as LoggerAwareInterface; @@ -52,6 +54,8 @@ use ZfcRbac\Service\AuthorizationServiceAwareTrait; */ class FincILS extends PAIA implements LoggerAwareInterface { + use SearchTrait; + const ILS_IDENTIFIER_BARCODE = 'barcode'; // vCard ADR is an ordered list of the following values // 0 - the post office box; @@ -78,7 +82,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * identifier retrieved by @getILSRecordId() * * @var array - * @access private * @deprecated */ private $idMapper = []; @@ -96,7 +99,7 @@ class FincILS extends PAIA implements LoggerAwareInterface * * @var array */ - protected $dynamicFields = ['barcode']; + protected $dynamicFields = [self::ILS_IDENTIFIER_BARCODE]; /** * ISIL used for identifying the correct ILS-identifier if array is returned @@ -222,7 +225,6 @@ class FincILS extends PAIA implements LoggerAwareInterface ? $this->config['General']['ilsTestTimeout'] : 90; } - /** * This optional method (introduced in VuFind 1.4) gets the online status of the * ILS – “ils-offline†for systems where the main ILS is offline, “ils-none†for @@ -285,7 +287,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * @param array $item Item * * @return bool - * @access protected */ protected function checkEmailHoldValidationCriteria($item) { @@ -306,7 +307,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * settings in FincILS.ini. * * @return array - * @access protected */ protected function getEmailHoldValidationCriteria() { @@ -372,15 +372,70 @@ class FincILS extends PAIA implements LoggerAwareInterface /** * PAIA support method - try to find fincid for last segment of PAIA id * - * @param string $id itemId - * @param string $idType id type to override ILS settings + * @param string|array $id itemId + * @param string $idType id type to override ILS settings, default is barcode * - * @return string $id + * @return string|array */ - protected function getAlternativeItemId($id, $idType = null) + protected function getAlternativeItemId($ids, $idType = null) { - $array = explode(":", $id); - return $this->getFincId(end($array), $idType); + $retval = []; + + try { + if (!is_array($ids)) { + $ids = [$ids]; + $isSingleId = true; + } + + if ($idType === null) { + $idType = $this->ilsIdentifier; + } + + if ($idType !== self::ILS_IDENTIFIER_BARCODE) { + foreach ($ids as &$id) { + if ($pos = strrpos($id, ':')) { + $id = substr($id,$pos + 1); + } + } + } + + $uniqueIds = array_unique($ids); + foreach ($uniqueIds as &$id) { + $id = addcslashes($id, ':'); + } + + if ($idType != "default") { + // different ilsIdentifier is configured, retrieve fincid + // if the given ilsIdentifier is known as a dynamic field it is suffixed + // with the isil + if (in_array($idType, $this->dynamicFields)) { + if (isset($this->mainConfig->CustomIndex->indexExtension)) { + $idType .= "_" + . trim($this->mainConfig->CustomIndex->indexExtension); + } + } + } + + $new = $this->searchRelatedRecords($idType, $uniqueIds, 9999, [], 'Solr', false); + if (count($new['first_results']) > 0) { + $retval = array_fill(0, count($ids), false); + foreach ($new['first_results'] as $record) { + /** @var \finc\RecordDriver\SolrDefault $record */ + $callNumbers = $record->getField($idType); + $matches = array_intersect($ids, $callNumbers); + foreach ($matches as $number => $match) { + /* map identifier to solr id */ + $retval[$number] = $record->getUniqueID(); + } + } + ksort($retval); + return ($isSingleId ?? false) ? $retval[0] : array_values($retval); + } + } catch (\Exception $e) { + $this->debug($e); + } + + return $retval; } /** @@ -412,7 +467,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * @param string $id Identifier * * @return mixed parent::getStatus() - * @access protected */ protected function doGetStatus($id) { @@ -518,15 +572,13 @@ class FincILS extends PAIA implements LoggerAwareInterface // fee.item 0..1 URI item that caused the fee // fee.feeid 0..1 URI URI of the type of service that // caused the fee - $additionalData['feeid'] = (isset($fee['feeid']) - ? $fee['feeid'] : null); - $additionalData['about'] = (isset($fee['about']) - ? $fee['about'] : null); - $additionalData['item'] = (isset($fee['item']) - ? $fee['item'] : null); + $additionalData['feeid'] = ($fee['feeid'] ?? null); + $additionalData['about'] = ($fee['about'] ?? null); + $additionalData['item'] = ($fee['item'] ?? null); return $additionalData; } + /** * Get Patron Profile * @@ -580,14 +632,6 @@ class FincILS extends PAIA implements LoggerAwareInterface (string)$vcard->{'X-LIBRARY-ILS-PATRON-EDIT-ALLOW'} ); } - if (isset($vcard->{'X-LIBRARY-BORROWER-BLACK-LIST-INDICATOR'})) { - $statuscodeInd - = (string)$vcard->{'X-LIBRARY-BORROWER-BLACK-LIST-INDICATOR'}; - } - if (isset($vcard->{'X-LIBRARY-BORROWER-BLACK-LIST-DESCRIPTION'})) { - $statuscodeDesc - = (string)$vcard->{'X-LIBRARY-BORROWER-BLACK-LIST-DESCRIPTION'}; - } } catch (Exception $e) { throw $e; } @@ -632,23 +676,19 @@ class FincILS extends PAIA implements LoggerAwareInterface } $idm = [ - 'firstname' => $patron['firstname'], - 'lastname' => $patron['lastname'], + 'firstname' => $patron['firstname'], + 'lastname' => $patron['lastname'], 'group' => (!empty($group)) ? $group : null, // PAIA specific custom values - 'expires' => isset($patron['expires']) + 'expires' => isset($patron['expires']) ? $this->convertDate($patron['expires']) : null, - 'statuscode' => isset($patron['status']) - ? $patron['status'] : null, - 'statuscodeInd' => isset($statuscodeInd) - ? $statuscodeInd : null, - 'statuscodeDesc' => isset($statuscodeDesc) - ? $statuscodeDesc : null, - 'note' => isset($patron['note']) - ? $patron['note'] : null, - 'canWrite' => in_array(self::SCOPE_WRITE_ITEMS, $this->getSession()->scope), + 'statuscode' => $patron['status'] ?? null, + 'statuscodeInd' => $statuscodeInd ?? null, + 'statuscodeDesc' => $statuscodeDesc ?? null, + 'note' => $patron['note'] ?? null, + 'canWrite' => in_array(self::SCOPE_WRITE_ITEMS, $this->getSession()->scope), // fincILS and PAIA specific custom values - 'email' => !empty($patron['email']) + 'email' => !empty($patron['email']) ? $patron['email'] : (!empty($emails[0]) ? $emails[0] : null), 'editableFields' => (!empty($editable)) ? $editable : null, 'home_library' => (!empty($home_library)) ? $home_library : null @@ -664,7 +704,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * @param array $vcard_fields Fields of vcard which can be edited. * * @return array $fields Translated form Field which can be modified - * @access private */ private function getEditableProfileFields($vcard_fields) { @@ -714,12 +753,11 @@ class FincILS extends PAIA implements LoggerAwareInterface * @param array $patron Patron data * * @return boolean true OK, false FAIL - * @access public */ public function setMyProfile($inval, $patron) { - $params['memberCode'] = $patron['cat_username']; - $params['password'] = $patron['cat_password']; + $params['memberCode'] = $patron['cat_username']; + $params['password'] = $patron['cat_password']; if (isset($patron['address']) && strpos($patron['address'], 'BEGIN:VCARD') === 0) { $vcard = \Sabre\VObject\Reader::read($patron['address']); @@ -764,7 +802,7 @@ class FincILS extends PAIA implements LoggerAwareInterface ? $this->config['PAIA']['profileFormEmptyInputReplacement'] : null; foreach ($inval as $key => $val) { - if (empty($val) && !is_null($replace)) { + if (empty($val) && null !== $replace) { $val = $replace; } @@ -860,7 +898,7 @@ class FincILS extends PAIA implements LoggerAwareInterface return true; } - $this->debug(__FUNCTION__.' '.$result['sysMessage']); + $this->debug(__FUNCTION__ . ' ' . $result['sysMessage']); return false; } @@ -870,7 +908,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * @param string $address * * @return mixed - * @access private */ private function splitAddress($address) { @@ -886,7 +923,7 @@ class FincILS extends PAIA implements LoggerAwareInterface $conf = $this->config; $regex = '(\D+\d[^\,]*)(?:\,\s*(.*))?'; $matches = []; - if (preg_match('/'.$regex.'/', $address, $matches)) { + if (preg_match('/' . $regex . '/', $address, $matches)) { return $matches; } return false; @@ -960,7 +997,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * * @return mixed Associative array of patron info on successful login, * null on unsuccessful login. - * @access public * @throws \Exception * @throws ILSException */ @@ -1017,7 +1053,6 @@ class FincILS extends PAIA implements LoggerAwareInterface * * @return mixed Associative array of patron info on successful login, * null on unsuccessful login. - * @access public */ public function refreshLogin($username, $password) { @@ -1071,7 +1106,7 @@ class FincILS extends PAIA implements LoggerAwareInterface if (!isset($itemsResponse) || $itemsResponse == null) { try { $itemsResponse = $this->paiaGetAsArray( - 'core/'.$patron['cat_username'].'/items' + 'core/' . $patron['cat_username'] . '/items' ); } catch (\Exception $e) { // all error handling is done in paiaHandleErrors so pass on the excpetion @@ -1094,7 +1129,7 @@ class FincILS extends PAIA implements LoggerAwareInterface // check exclude filters $excludeCounter = 0; foreach ($filterValue as $excludeKey => $excludeValue) { - if ((isset($doc[$excludeKey]) && in_array($doc[$excludeKey], (array) $excludeValue)) + if ((isset($doc[$excludeKey]) && in_array($doc[$excludeKey], (array)$excludeValue)) || ($excludeValue == null && !isset($doc[$excludeKey])) ) { $excludeCounter++; @@ -1125,7 +1160,7 @@ class FincILS extends PAIA implements LoggerAwareInterface default: // any other filter is a positive filter, so the item // might be selected if the key-value pair does match - if ((isset($doc[$filterKey]) && in_array($doc[$filterKey], (array) $filterValue)) + if ((isset($doc[$filterKey]) && in_array($doc[$filterKey], (array)$filterValue)) || ($filterValue == null && !isset($doc[$filterKey])) ) { $filterCounter++; @@ -1167,8 +1202,7 @@ class FincILS extends PAIA implements LoggerAwareInterface } /** - * Helper function to postprocess the PAIA items for display in catalog (e.g. retrieve - * fincid etc.). + * Helper function to postprocess the PAIA items for display in catalog (e.g. retrieve solr id etc.). * * @param array $items Array of PAIA items to be postprocessed * @@ -1181,11 +1215,11 @@ class FincILS extends PAIA implements LoggerAwareInterface // item_id identifier - Solr field mapping $identifier = [ - 'barcode' => 'barcode' . + self::ILS_IDENTIFIER_BARCODE => self::ILS_IDENTIFIER_BARCODE . (isset($this->mainConfig->CustomIndex->indexExtension) - ? '_'.$this->mainConfig->CustomIndex->indexExtension : ''), - 'fincid' => 'id', - 'ppn' => 'record_id' + ? '_' . $this->mainConfig->CustomIndex->indexExtension : ''), + 'fincid' => 'id', + 'ppn' => 'record_id' ]; // try item_id with defined regex pattern and identifiers and use Solr to @@ -1194,8 +1228,8 @@ class FincILS extends PAIA implements LoggerAwareInterface foreach ($identifier as $key => $value) { $matches = []; if (preg_match(sprintf($idPattern, $key), $itemId, $matches)) { - return $this->getFincId( - addcslashes($matches[3],':'), + return $this->getAlternativeItemId( + $matches[3], $value ); } @@ -1243,9 +1277,9 @@ class FincILS extends PAIA implements LoggerAwareInterface $returnArray = []; if (count($items) && !empty($fieldName)) { - for ($i=0; $i<count($items); $i++) { + for ($i = 0; $i < count($items); $i++) { if (isset($items[$i][$fieldName])) { - $sortArray[$i]=$items[$i][$fieldName]; + $sortArray[$i] = $items[$i][$fieldName]; } else { array_push($noSortArray, $items[$i]); } @@ -1311,7 +1345,7 @@ class FincILS extends PAIA implements LoggerAwareInterface $fines = $this->getMyFines($patron); $total = 0.0; foreach ($fines as $fee) { - $total += (float) $fee['amount']; + $total += (float)$fee['amount']; } return $total / 100; } @@ -1347,12 +1381,12 @@ class FincILS extends PAIA implements LoggerAwareInterface $dateObj = new DateTime($date, new DateTimeZone($timezone)); $intervalSpec = 'P' . - ($years != null ? $years . 'Y' : '') . - ($months != null ? $months . 'M' : '') . - ($days != null ? $days . 'D' : '') . - ($hours != null ? $hours . 'H' : '') . + ($years != null ? $years . 'Y' : '') . + ($months != null ? $months . 'M' : '') . + ($days != null ? $days . 'D' : '') . + ($hours != null ? $hours . 'H' : '') . ($minutes != null ? 'T' . $minutes . 'M' : '') . - ($seconds != null ? $seconds . 'S' : ''); + ($seconds != null ? $seconds . 'S' : ''); $dateInterval = new DateInterval($intervalSpec); return $dateObj->add($dateInterval)->format('Y-m-d\TH:i:s'); @@ -1386,17 +1420,17 @@ class FincILS extends PAIA implements LoggerAwareInterface ); $context = [ 'authenticator' => $this->auth, - 'record' => $this->getRecord($id) + 'record' => $this->getRecord($id), ]; $context = $eval($context); return [[ - 'id' => $id, + 'id' => $id, 'availability' => $context['available'], - 'status' => $context['available'] ? 'available' : 'unavailable', - 'reserve' => 'false', - 'location' => '', - 'callnumber' => '', - 'services' => (array)$context['decider'] + 'status' => $context['available'] ? 'available' : 'unavailable', + 'reserve' => 'false', + 'location' => '', + 'callnumber' => '', + 'services' => (array)$context['decider'] ]]; } $permission = $this->getRecord($id)->tryMethod('getRecordPermission'); @@ -1405,13 +1439,13 @@ class FincILS extends PAIA implements LoggerAwareInterface ? $this->auth->isGranted($permission) : true; return [[ - 'id' => $id, + 'id' => $id, 'availability' => $isGranted, - 'status' => $isGranted ? 'available' : $permission, - 'reserve' => 'false', - 'location' => '', - 'callnumber' => '', - 'services' => !$isGranted ? [$permission] : [] + 'status' => $isGranted ? 'available' : $permission, + 'reserve' => 'false', + 'location' => '', + 'callnumber' => '', + 'services' => !$isGranted ? [$permission] : [] ]]; } @@ -1618,59 +1652,6 @@ class FincILS extends PAIA implements LoggerAwareInterface return $ids; } - /** - * Get the finc id of the record with the given ilsIdentifier value - * - * @param string $ilsId Document to look up. - * @param string $ilsIdentifier Identifier to override config settings. - * - * @return string $fincId if ilsIdentifier is configured, otherwise $ilsId - */ - private function getFincId($ilsId, $ilsIdentifier = null) - { - // override ilsIdentifier with the ilsIdentifier set in ILS driver config - if ($ilsIdentifier == null) { - $ilsIdentifier = $this->ilsIdentifier; - } - - if ($ilsIdentifier != "default") { - // different ilsIdentifier is configured, retrieve fincid - - // if the given ilsIdentifier is known as a dynamic field it is suffixed - // with the isil - if (in_array($ilsIdentifier, $this->dynamicFields)) { - if (isset($this->mainConfig->CustomIndex->indexExtension)) { - $ilsIdentifier .= "_" - . trim($this->mainConfig->CustomIndex->indexExtension); - } - } - try { - // todo: compatible implementation for any SearchBackend (currently Solr only) - $query = $ilsIdentifier . ':' . $ilsId; - $result = $this->searchService->search( - 'Solr', - new Query($query) - ); - if (count($result) === 0) { - throw new \Exception( - 'Problem retrieving finc id for record with ' . $query - ); - } - return current($result->getRecords())->getUniqueId(); - } catch (\Exception $e) { - $this->debug($e); - // refs #12318 return falls if no main identifier can delivered - // null will logically correct but throws exceptions in - // subsequential core methods - return false; - } - } - // todo: check if return $ilsId is reasonable in context. - // return will be only processed if $ilsIdentifier is defined as - // 'default'. therefore method hasn't been called properly. - return $ilsId; - } - /** * Private service test method * diff --git a/module/finc/src/finc/ILS/Driver/FincLibero.php b/module/finc/src/finc/ILS/Driver/FincLibero.php index 8f562ec316f6d5d3018d1cb9004bc719fdbd4adf..8c552e7387710567d5bbd0afafb0fb8b81e6b827 100644 --- a/module/finc/src/finc/ILS/Driver/FincLibero.php +++ b/module/finc/src/finc/ILS/Driver/FincLibero.php @@ -1,6 +1,6 @@ <?php /** - * Finc specific Libero ILS Driver for VuFind, using PAIA, DAIA and LiberoDing + * Finc specific Libero ILS Driver for VuFind, using PAIA, DAIA and LiberoWachtl * services. * * PHP version 5 @@ -30,9 +30,10 @@ namespace finc\ILS\Driver; use VuFind\I18n\Translator\TranslatorAwareTrait; use VuFind\I18n\Translator\TranslatorAwareInterface, VuFind\Exception\ILS as ILSException; +use VuFindSearch\Query\Query; /** - * Finc specific Libero ILS Driver for VuFind, using PAIA, DAIA and LiberoDing + * Finc specific Libero ILS Driver for VuFind, using PAIA, DAIA and LiberoWachtl * services. * * @category VuFind2 @@ -45,7 +46,7 @@ class FincLibero extends FincILS implements TranslatorAwareInterface { const DELETE_NOTIFICATIONS_SUCCESS = '1'; const DELETE_NOTIFICATIONS_ERROR = '0'; - use LiberoDingTrait; + use LiberoWachtlTrait; use TranslatorAwareTrait; /** @@ -106,6 +107,25 @@ class FincLibero extends FincILS implements TranslatorAwareInterface */ protected $readingRoomURIs = []; + /** + * Regex pattern to detect bound item ID + * @var string + */ + protected $boundItemIdPattern; + + /** + * Regex pattern to detect bound item label + * @var string + */ + protected $boundItemLabelPattern; + + public function init() + { + parent::init(); + $this->boundItemIdPattern = $this->config['General']['bound_item_id_pattern'] ?? null; + $this->boundItemLabelPattern = $this->config['General']['bound_item_label_pattern'] ?? null; + } + /** * Helper function to extract the Namespace from the DAIA URI prefix. * @@ -701,14 +721,199 @@ class FincLibero extends FincILS implements TranslatorAwareInterface return $pickUpLocations; } + /** - * TODO: check status of this function - * de_15 -> getBoundItemId() vs. de_l152 -> getBoundItemInfo() - * @param $item - * @return array + * Helper method to check whether this item is bound with another item and needs + * to be ordered via this bound item + * + * @param array $item Array with DAIA item data + * + * @return null */ protected function getBoundItemId($item) { - return []; + if (!isset($this->boundItemIdPattern)) return null; + $availabilities = ['available', 'unavailable']; + + // start logic to check if we have a bound item that needs to be ordered + // via another record (see https://intern.finc.info/issues/5068) + foreach ($availabilities as $availability) { + if (isset($item[$availability])) { + foreach ($item[$availability] as $available) { + if (isset($available['limitation'])) { + foreach ($available['limitation'] as $limitation) { + if (in_array($limitation['id'], $this->awlLimitations)) { + // items with limitations identifying the record for + // being bound to another item contain the RSN of the + // bound item in their current available service href + if (isset($available['href']) + && preg_match($this->boundItemIdPattern, + $available['href'], + $matches + ) + ) { + return $this->queryBoundItem($matches['id']); + } + } + } + } + } + } + } + + // if we made it this far this item definitely is not bound to another item + return null; + } + + + /** + * Helper method to query bound item by rsn for record_id + * + * @param string $key + * + * @return string record_id + */ + protected function queryBoundItem($key) { + + $query = $this->getBoundItemQueryString($key); + if (empty($query)) { + return null; + } + try { + // let's search with the built query + $result = $this->searchService + ->search('VuFind', new Query($query)); + if (count($result) === 0) { + $this->debug('Problem retrieving rsn ' . + 'for record with query:' . $query); + return null; + } + // pass the id from the first found record as + // awlRecordId to the controller + return current($result->getRecords()) + ->getUniqueId(); + } catch (\Exception $e) { + $this->debug($e); + return null; + } + } + + /** + * Check if item is a pretended dummy to signify there is a title order + * possible. + * + * @param array $item DAIA item + * + * @return mixed + */ + public function isTitleHold($item) + { + foreach ($this->titleHoldLimitations as $limitation) { + $regex = '/^' . trim($limitation) . '$/'; + if (0 < preg_match($regex, $item['id'])) { + return true; + } + } + return null; + } + + /** + * Place Title Hold + * + * Attempts to place a hold or recall on a particular item and returns + * an array with result details + * + * Make a request on a specific record + * + * @param array $holdDetails An array of item and patron data + * + * @return mixed An array of data on the request including + * whether or not it was successful and a system message (if available) + */ + public function placeTitleHold($holdDetails) + { + $item = $holdDetails['item_id']; + $patron = $holdDetails['patron']; + $doc = []; + $doc['item'] = stripslashes($item); + if ($confirm = $this->getConfirmations( + $this->extendPickUpLocation($holdDetails) + )) { + $doc["confirm"] = $confirm; + } + $post_data['doc'][] = $doc; + try { + $array_response = $this->paiaPostAsArray( + 'core/'.$patron['cat_username'].'/request', $post_data + ); + } catch (Exception $e) { + $this->debug($e->getMessage()); + return [ + 'success' => false, + 'sysMessage' => $e->getMessage(), + ]; + } + $details = []; + if (array_key_exists('error', $array_response)) { + $details = [ + 'success' => false, + 'sysMessage' => $array_response['error_description'] + ]; + } else { + $elements = $array_response['doc']; + foreach ($elements as $element) { + if (array_key_exists('error', $element)) { + $details = [ + 'success' => false, + 'sysMessage' => $element['error'] + ]; + } else { + // FincLibero supports more expressive responses from paialibero + // which should be shown instead to the user (localIlsStatus) + $details = [ + 'success' => true, + 'sysMessage' => isset($element['localIlsStatus']) + ? $element['localIlsStatus'] : 'Successfully requested' + ]; + // if caching is enabled for DAIA remove the cached data for the + // current item otherwise the changed status will not be shown + // before the cache expires + if ($this->daiaCacheEnabled) { + $this->removeCachedData($holdDetails['doc_id']); + } + } + } + } + return $details; + } + + /** + * Extend pickup location with base department URI + * + * @param array $holdDetails An array of item and patron data. + * + * @return array $holdDetails; + */ + protected function extendPickUpLocation($holdDetails) + { + if (isset($holdDetails['pickUpLocation'])) { + $holdDetails['pickUpLocation'] + = $this->departmentLocationBase . $holdDetails['pickUpLocation']; + } + return $holdDetails; + } + + /** + * Helper function for queryBoundItem. Prepares instance specific query string + * to get the ID of the bound item + * + * Override in inherited FincLibero + * + * @param $key string ID to search for + * @return string SOLR query to be used as q parameter + */ + protected function getBoundItemQueryString($key) + { + return ''; } } diff --git a/module/finc/src/finc/ILS/Driver/LiberoDingTrait.php b/module/finc/src/finc/ILS/Driver/LiberoWachtlTrait.php similarity index 79% rename from module/finc/src/finc/ILS/Driver/LiberoDingTrait.php rename to module/finc/src/finc/ILS/Driver/LiberoWachtlTrait.php index 3940de37c6ff605239895f050f3ee746be7f826f..6135ea641d59fa38bd564fea19572276f6edb3af 100644 --- a/module/finc/src/finc/ILS/Driver/LiberoDingTrait.php +++ b/module/finc/src/finc/ILS/Driver/LiberoWachtlTrait.php @@ -1,7 +1,7 @@ <?php /** - * Finc specific LiberoDing trait providing all the functions necessary for - * communicating with the LiberoDing. + * Finc specific LiberoWachtl trait providing all the functions necessary for + * communicating with the LiberoWachtl. * * PHP version 5 * @@ -32,8 +32,8 @@ use VuFind\Exception\ILS as ILSException; use Zend\Log\LoggerAwareInterface as LoggerAwareInterface; /** - * Finc specific LiberoDing trait providing all the functions necessary for - * communicating with the LiberoDing. + * Finc specific LiberoWachtl trait providing all the functions necessary for + * communicating with the LiberoWachtl. * * @category VuFind * @package ILS_Drivers @@ -41,8 +41,13 @@ use Zend\Log\LoggerAwareInterface as LoggerAwareInterface; * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki */ -trait LiberoDingTrait +trait LiberoWachtlTrait { + /** + * @var $liberoWachtlUrl string URL of wachtl API + */ + protected $liberoWachtlUrl; + /** * Get connection timeout of Libero request. * @@ -53,9 +58,9 @@ trait LiberoDingTrait */ protected function getConnectTimeout($connectTimeout = 500) { - return $test = (isset($this->config['LiberoDing']['connectTimeout']) - && is_numeric($this->config['LiberoDing']['connectTimeout'])) - ? $this->config['LiberoDing']['connectTimeout'] + return $test = (isset($this->config['LiberoWachtl']['connectTimeout']) + && is_numeric($this->config['LiberoWachtl']['connectTimeout'])) + ? $this->config['LiberoWachtl']['connectTimeout'] : $connectTimeout; } @@ -69,25 +74,28 @@ trait LiberoDingTrait */ protected function getResponseTimeout($responseTimeout = 1000) { - return (isset($this->config['LiberoDing']['responseTimeout']) - && is_numeric($this->config['LiberoDing']['responseTimeout'])) - ? $this->config['LiberoDing']['responseTimeout'] + return (isset($this->config['LiberoWachtl']['responseTimeout']) + && is_numeric($this->config['LiberoWachtl']['responseTimeout'])) + ? $this->config['LiberoWachtl']['responseTimeout'] : $responseTimeout; } /** - * gets the webscraper url from config + * gets the LiberoWachtl url from config * * @return string - * @throws \Exception Webscraper url not defined + * @throws \Exception LiberoWachtl url not defined */ - protected function getWebScraperUrl() + protected function getLiberoWachtlUrl() { - if (!isset($this->config['LiberoDing']['webScraperUrl'])) { - throw new \Exception('no webscraper url defined'); + if (!isset($this->liberoWachtlUrl)) { + if (!isset($this->config['LiberoWachtl']['liberoWachtlUrl'])) { + throw new \Exception('no LiberoWachtl url defined'); + } + $this->liberoWachtlUrl = $this->config['LiberoWachtl']['liberoWachtlUrl']; } - return $this->config['LiberoDing']['webScraperUrl']; + return $this->liberoWachtlUrl; } /** @@ -98,11 +106,11 @@ trait LiberoDingTrait */ protected function getDbName() { - if (!isset($this->config['LiberoDing']['databaseName'])) { + if (!isset($this->config['LiberoWachtl']['databaseName'])) { throw new \Exception('no database name defined'); } - return $this->config['LiberoDing']['databaseName']; + return $this->config['LiberoWachtl']['databaseName']; } /** @@ -112,7 +120,7 @@ trait LiberoDingTrait * @return boolean Returns true if a connection exists * @throws \Exception Throws ILSException */ - public function checkLiberoDingConnection() + public function checkLiberoWachtlConnection() { $http_header['User-Agent'] = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)'; @@ -125,7 +133,7 @@ trait LiberoDingTrait try { $result = $this->httpService->post( - $this->getWebScraperUrl() .'liberoPing.jsp', + $this->getLiberoWachtlUrl() .'liberoPing.jsp', http_build_query($params), 'application/json; charset=UTF-8', null, @@ -162,17 +170,17 @@ trait LiberoDingTrait */ public function balanceFinesOfUser($patron, $amount) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; $params['amount'] = $amount; try { $result = $this->httpService->get( - $this->getWebScraperUrl() .'payAnyFee.jsp', + $this->getLiberoWachtlUrl() .'payAnyFee.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -189,7 +197,7 @@ trait LiberoDingTrait // reload PAIA session by paia login again $this->refreshLogin($patron['cat_username'], $patron['cat_password']); - return $this->getLiberoDingResultBool($result); + return $this->getLiberoWachtlResultBool($result); } /** @@ -203,16 +211,16 @@ trait LiberoDingTrait */ protected function getSystemMessages($patron) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; try { $result = $this->httpService->get( - $this->getWebScraperUrl() .'getMySystemMessages.jsp', + $this->getLiberoWachtlUrl() .'getMySystemMessages.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -227,7 +235,7 @@ trait LiberoDingTrait return false; } - return $this->getLiberoDingResult($result, 'getMySystemMessages'); + return $this->getLiberoWachtlResult($result, 'getMySystemMessages'); } /** @@ -248,7 +256,7 @@ trait LiberoDingTrait $messageIdList = null, $toDate = null ) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; @@ -261,10 +269,10 @@ trait LiberoDingTrait try { $result = $this->httpService->get( - $this->getWebScraperUrl() .'removeMySystemMessages.jsp', + $this->getLiberoWachtlUrl() .'removeMySystemMessages.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -279,7 +287,7 @@ trait LiberoDingTrait return false; } - return $this->getLiberoDingResult($result, 'removeMySystemMessages'); + return $this->getLiberoWachtlResult($result, 'removeMySystemMessages'); } /** @@ -293,15 +301,15 @@ trait LiberoDingTrait */ public function setLockMyAccount($patron) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; try { $result = $this->httpService->get( - $this->getWebScraperUrl() .'lockMyAccount.jsp', + $this->getLiberoWachtlUrl() .'lockMyAccount.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -315,7 +323,7 @@ trait LiberoDingTrait return false; } // Log error for debugging - if( true === ($bool = $this->getLiberoDingResultBool($result))) { + if( true === ($bool = $this->getLiberoWachtlResultBool($result))) { // Remove explicitly session vars of PAIA connection // Compliance security issue $session = $this->getSession(); @@ -357,7 +365,7 @@ trait LiberoDingTrait { $map = self::profileDataMapper(true); - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; @@ -376,10 +384,10 @@ trait LiberoDingTrait try { $result = $this->httpService->get( - $this->getWebScraperUrl() .'setMyProfile.jsp', + $this->getLiberoWachtlUrl() .'setMyProfile.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -394,7 +402,7 @@ trait LiberoDingTrait return false; } - return $this->getLiberoDingResultBool($result); + return $this->getLiberoWachtlResultBool($result); } /** @@ -404,8 +412,8 @@ trait LiberoDingTrait */ public function getIgnoredProfileFields() { - return isset($this->config['LiberoDing']['ignoredProfileFields']) ? - $this->config['LiberoDing']['ignoredProfileFields'] : []; + return isset($this->config['LiberoWachtl']['ignoredProfileFields']) ? + $this->config['LiberoWachtl']['ignoredProfileFields'] : []; } /** @@ -415,8 +423,8 @@ trait LiberoDingTrait */ public function getRestrictedUserGroups() { - return isset($this->config['LiberoDing']['restrictedUserGroups']) ? - $this->config['LiberoDing']['restrictedUserGroups'] : []; + return isset($this->config['LiberoWachtl']['restrictedUserGroups']) ? + $this->config['LiberoWachtl']['restrictedUserGroups'] : []; } /** @@ -427,12 +435,12 @@ trait LiberoDingTrait */ public function getItemsPerViewLoanHistory() { - return isset($this->config['LiberoDing']['itemsPerPageLoanHistory']) ? - $this->config['LiberoDing']['itemsPerPageLoanHistory'] : 20; + return isset($this->config['LiberoWachtl']['itemsPerPageLoanHistory']) ? + $this->config['LiberoWachtl']['itemsPerPageLoanHistory'] : 20; } /** - * This method queries the LiberoDing-ILS for a patron's current profile + * This method queries the LiberoWachtl-ILS for a patron's current profile * information. * * @param array $patron Patron array returned by patronLogin method. @@ -443,18 +451,18 @@ trait LiberoDingTrait * @see For content variables see method profileDataMapper * @throws \Exception Throws ILSException */ - protected function getLiberoDingProfile($patron, $mapped = true) + protected function getLiberoWachtlProfile($patron, $mapped = true) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; try { $result = $this->httpService->get( - $this->getWebScraperUrl() .'getMyProfile.jsp', + $this->getLiberoWachtlUrl() .'getMyProfile.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -473,7 +481,7 @@ trait LiberoDingTrait // define of disabled fields $mappeddata = []; $map = self::profileDataMapper(); - $data = $this->getLiberoDingResult($result, 'getMyProfile'); + $data = $this->getLiberoWachtlResult($result, 'getMyProfile'); foreach ($data as $key => $value) { if ($key == 'disabledInputs') { @@ -492,12 +500,12 @@ trait LiberoDingTrait return $mappeddata; } - return $this->getLiberoDingResult($result, 'getMyProfile'); + return $this->getLiberoWachtlResult($result, 'getMyProfile'); } /** - * This method sends a PIN changing request to the LiberoDing. + * This method sends a PIN changing request to the LiberoWachtl. * * @param string $newPin New pin. * @param array $patron Patron array returned by patronLogin method. @@ -507,17 +515,17 @@ trait LiberoDingTrait */ public function changeUserPin($newPin, $patron) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; $params['newPin'] = $newPin; try { $result = $this->httpService->get( - $this->getWebScraperUrl() .'changeUserPin.jsp', + $this->getLiberoWachtlUrl() .'changeUserPin.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -532,7 +540,7 @@ trait LiberoDingTrait return false; } - return $this->getLiberoDingResult($result, 'changeUserPinOk'); + return $this->getLiberoWachtlResult($result, 'changeUserPinOk'); } /** @@ -575,11 +583,11 @@ trait LiberoDingTrait } /** - * Private Helper function to return LiberoDing request parameters + * Private Helper function to return LiberoWachtl request parameters * * @return array */ - private function getLiberoDingRequestParams() + private function getLiberoWachtlRequestParams() { return [ 'dbName' => $this->getDbName(), @@ -589,11 +597,11 @@ trait LiberoDingTrait } /** - * Private Helper function to return LiberoDing request headers + * Private Helper function to return LiberoWachtl request headers * * @return array */ - private function getLiberoDingRequestHeaders() + private function getLiberoWachtlRequestHeaders() { return [ 'User-Agent' => 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)' @@ -609,7 +617,7 @@ trait LiberoDingTrait * * @return mixed */ - private function getLiberoDingResult($result, $resultKey) + private function getLiberoWachtlResult($result, $resultKey) { // get result as array $details = json_decode($result->getBody(), true); @@ -630,7 +638,7 @@ trait LiberoDingTrait * @return array * @access private */ - private function getLiberoDingResultBool($result) + private function getLiberoWachtlResultBool($result) { // get result as array $details = json_decode($result->getBody(), true); @@ -646,7 +654,7 @@ trait LiberoDingTrait * Customized getMyLoanHistory * * @param array $patron Array returned from patronLogin() - * @param int $lastLine Integer until LiberoDing should process history + * @param int $lastLine Integer until LiberoWachtl should process history * incrementally * * @return array $retval @@ -657,16 +665,16 @@ trait LiberoDingTrait */ public function getMyLoanHistory($patron, $lastLine) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; $params['pageNumber'] = 1; try { $result = $this->httpService->get( - $this->getWebScraperUrl() . 'getMyLoanHistory.jsp', + $this->getLiberoWachtlUrl() . 'getMyLoanHistory.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -699,10 +707,10 @@ trait LiberoDingTrait $params['pageNumber'] = $page; try { $result = $this->httpService->get( - $this->getWebScraperUrl() . 'getMyLoanHistory.jsp', + $this->getLiberoWachtlUrl() . 'getMyLoanHistory.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); @@ -718,7 +726,7 @@ trait LiberoDingTrait // merge with previous request $retval['items'] = array_merge( $retval['items'], - $this->getLiberoDingResult($result, 'getMyLoanHistory') + $this->getLiberoWachtlResult($result, 'getMyLoanHistory') ); // If there are requested lines collected break the iteration if (count($retval['items']) > $lastLine) { @@ -726,10 +734,10 @@ trait LiberoDingTrait } } } - // refs #11535 enable getDriverForILSRecord - foreach ($retval['items'] as &$current) { - $current['id'] = - $this->getAlternativeItemId($current['barcode'], "barcode"); + + $ids = $this->getAlternativeItemId(array_column($retval['items'], FincILS::ILS_IDENTIFIER_BARCODE), FincILS::ILS_IDENTIFIER_BARCODE); + foreach ($retval['items'] as $number => &$current) { + $current['id'] = $ids[$number] ?? false; } return $retval; } @@ -748,16 +756,16 @@ trait LiberoDingTrait */ public function getMyLoanHistoryItems($patron) { - $params = $this->getLiberoDingRequestParams(); + $params = $this->getLiberoWachtlRequestParams(); $params['memberCode'] = $patron['cat_username']; $params['password'] = $patron['cat_password']; $params['pageNumber'] = 1; try { $result = $this->httpService->get( - $this->getWebScraperUrl() . 'getMyLoanHistory.jsp', + $this->getLiberoWachtlUrl() . 'getMyLoanHistory.jsp', $params, null, - $this->getLiberoDingRequestHeaders() + $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { throw new ILSException($e->getMessage()); diff --git a/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php b/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php index a81cf67cd39e26d782fa9c41e5af53dd1e920e36..c17bd5e99f6029bf368c46cdf01dccda9e2e53d8 100644 --- a/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php +++ b/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php @@ -28,6 +28,8 @@ */ namespace finc\RecordDriver; +use File_MARC_Field; +use finc\Controller\CustomTraits\SearchTrait as SearchTrait; use VuFindSearch\Query\Query as Query; /** @@ -42,6 +44,8 @@ use VuFindSearch\Query\Query as Query; */ trait SolrMarcFincTrait { + use SearchTrait; + /** * Returns true if the record supports real-time AJAX status lookups. * @@ -63,6 +67,39 @@ trait SolrMarcFincTrait return $this->getFirstFieldValue('040', ['e']) == 'rda'; } + /** + * Helper function for getURLs() + * Decides which sub results from getURLs to choose. + * Standard version takes + * instance-specific URLs if present or choses LEFER over default otherwise + * + * @param array $resultsPerIsil + * @return mixed|null + */ + protected function extractUrls(array $resultsPerIsil) + { + $subresult = null; + // if we have a local entry for the current instance (i.e. ISIL) + // use that one + foreach ($this->isil as $isil) { + if (isset($resultsPerIsil[$isil])) { + $subresult = $resultsPerIsil[$isil]; + break; + } + } + // LFER ("Lizenzfreie elektronische Ressourcen", + // "license free electronic resources") may have a designated entry, + // if there is no instance specific url try the one for LFER + if (empty($subresult) && isset($resultsPerIsil['LFER'])) { + $subresult = $resultsPerIsil['LFER']; + } + // if we still have no URL, look for the unspecific entry + if (empty($subresult) && isset($resultsPerIsil['default'])) { + $subresult = $resultsPerIsil['default']; + } + return $subresult; + } + /** * Return an array of associative URL arrays with one or more of the following * keys: @@ -150,25 +187,8 @@ trait SolrMarcFincTrait } } } - $subresult = null; - // if we have a local entry for the current instance (i.e. ISIL) - // use that one - foreach ($this->isil as $isil) { - if (isset($resultsPerIsil[$isil])) { - $subresult = $resultsPerIsil[$isil]; - break; - } - } - // LFER ("Lizenzfreie elektronische Ressourcen", - // "license free electronic resources") may have a designated entry, - // if there is no instance specific url try the one for LFER - if (empty($subresult) && isset($resultsPerIsil['LFER'])) { - $subresult = $resultsPerIsil['LFER']; - } - // if we still have no URL, look for the unspecific entry - if (empty($subresult) && isset($resultsPerIsil['default'])) { - $subresult = $resultsPerIsil['default']; - } + + $subresult = $this->extractUrls($resultsPerIsil); if (!empty($subresult)) { foreach ($subresult as $current) { @@ -1731,7 +1751,7 @@ trait SolrMarcFincTrait ] as $source) { $return = []; foreach ($retval as $entry) { - if (isset($entry['source']) && strpos($entry['source'],$source) !== false) { + if (isset($entry['source']) && strpos($entry['source'], $source) !== false) { $return[] = $entry; } } @@ -1772,7 +1792,7 @@ trait SolrMarcFincTrait return array_map( 'unserialize', array_unique( - array_map('serialize',$retval[$thesaurus]) + array_map('serialize', $retval[$thesaurus]) ) ); } @@ -2034,37 +2054,22 @@ trait SolrMarcFincTrait } } - public function searchRelatedRecords($field, $values, $limit = 20, - $filters = [], $backend_id = 'Solr' - ) { - - if (!empty($filters)) { - $fq = ''; - foreach ($filters as $filterField => $value) { - $fq = (empty($fq) ? '' : ' AND ') - . "$filterField:$value"; - } - } - - $query = new Query( - $field . ':(' . implode(' OR ', $values) - . ') AND NOT id:' . $this->getUniqueID() - . (isset($fq) ? " AND $fq" : '') - ); - - $result = $this->searchService->search($backend_id, $query, 0, $limit); - $return['first_results'] = $result->getRecords(); - - if (isset($limit) && $result->getTotal() > $limit) { - $return['more_query'] = $query->getString(); - } - return $return; - - } - + /** + * Finds related records from the K10plus source. Relations are specified in + * a specified subfield of applicable fields (standard |w) + * + * @param File_MARC_Field $line MARC field entry with relation + * information + * @param int $limit number of records to return as + * direct results, if there are more, + * a search link will be provided + * @param string $linkSubField name of the subfield that contains + * relation info + * @return array|mixed|null + */ public function getRelatedKxpRecord($line, $limit = 1, $linkSubField = 'w') { - if ($linkFields = $line->getSubfields('w')) { + if ($linkFields = $line->getSubfields($linkSubField)) { $linked = []; foreach ($linkFields as $current) { $text = $current->getData(); diff --git a/module/finc/src/finc/RecordDriver/SolrMarcUrlRulesDelegatorFactory.php b/module/finc/src/finc/RecordDriver/SolrMarcUrlRulesDelegatorFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..7160540c640834bc211dcb014672dc24b6bbc2d2 --- /dev/null +++ b/module/finc/src/finc/RecordDriver/SolrMarcUrlRulesDelegatorFactory.php @@ -0,0 +1,72 @@ +<?php +/** + * Delegator Factory for SolrMarc record drivers. Enables specialized rules + * for \finc\RecordDriver\SolrMarcFincTrait::getURLs + * + * PHP version 7 + * + * Copyright (C) Villanova University 2021. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package RecordDrivers + * @author Dorian Merz <mer@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace finc\RecordDriver; + +use Interop\Container\ContainerInterface; + +/** + * Delegator Factory for SolrMarc record drivers. Enables specialized rules + * for \finc\RecordDriver\SolrMarcFincTrait::getURLs + * + * @category VuFind + * @package RecordDrivers + * @author Dorian Merz <mer@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class SolrMarcUrlRulesDelegatorFactory +{ + /** + * Creates a delegator of VuFind/Search to register several listeners. + * + * @param ContainerInterface $container + * @param string $name + * @param callable $callback + * @param array|null $options + * + * @return mixed + */ + public function __invoke( + ContainerInterface $container, + $name, + callable $callback, + array $options = null + ) { + $instance = call_user_func($callback); + + $urlRules = $container->get('VuFind\YamlReader')->get('SolrMarcUrlRules.yaml'); + if (!empty($urlRules)) { + if (isset($urlRules['rules']) && isset($urlRules['stopFlags'])) { + $instance->urlRules = $urlRules; + } + } + + return $instance; + } +} diff --git a/module/finc/src/finc/Service/LuceneSyntaxHelperOverride.php b/module/finc/src/finc/Service/LuceneSyntaxHelperOverride.php new file mode 100644 index 0000000000000000000000000000000000000000..0629e85c8cf240640ce84b2b5f5ef3dde0416e69 --- /dev/null +++ b/module/finc/src/finc/Service/LuceneSyntaxHelperOverride.php @@ -0,0 +1,63 @@ +<?php +/** + * LuceneSyntaxHelper Override + * + * A Delegator Factory that registers several listeners at events triggered by the + * VuFind\Search service. + * + * PHP version 7 + * + * Copyright (C) Leipzig University Library 2021. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Finc/Service + * @author Dorian Merz <merz@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +namespace finc\Service; + +/** + * LuceneSyntaxHelper Override + * + * @category VuFind + * @package Finc/Service + * @author Dorian Merz <merz@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class LuceneSyntaxHelperOverride extends \VuFindSearch\Backend\Solr\LuceneSyntaxHelper +{ + public function __construct(\VuFindSearch\Backend\Solr\LuceneSyntaxHelper $parentHelper) + { + parent::__construct( + $parentHelper->caseSensitiveBooleans, + $parentHelper->caseSensitiveRanges + ); + } + + /** + * {@inheritDoc} + * overridden to avoid query rebuilding + * + * @param string $searchString + * @return false + */ + public function containsAdvancedLuceneSyntax($searchString) + { + return false; + } +} diff --git a/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php b/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php index 9bc29e20a830a8464dd35d3ca4f6f96f97e3d59c..a7e35943f00dcb5a95f4e1dcfb49fb93c9599912 100644 --- a/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php +++ b/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php @@ -65,6 +65,11 @@ class MungerInjectionDelegatorFactory implements DelegatorFactoryInterface */ protected $shards_to_register; + /** + * @var array search types to be excluded from lucene query syntax detection + */ + protected $override_advanced_lucene_detection; + /** * Creates a delegator of VuFind/Search to register several listeners. * @@ -106,6 +111,12 @@ class MungerInjectionDelegatorFactory implements DelegatorFactoryInterface $this->shards_to_register = $shards; $e->attach('VuFindSearch', 'pre', [$this, 'registerShards']); } + if (isset($searchConfig->General->override_advanced_lucene_detection)) { + $this->override_advanced_lucene_detection = array_flip($searchConfig->General->override_advanced_lucene_detection->toArray()); + if (!empty($this->override_advanced_lucene_detection)) { + $e->attach('VuFindSearch', 'pre', [$this, 'overrideLuceneSyntaxHelper']); + } + } return $instance; } @@ -180,4 +191,48 @@ class MungerInjectionDelegatorFactory implements DelegatorFactoryInterface $params->set('shards', implode(',', $this->shards_to_register)); } } + + public function overrideLuceneSyntaxHelper(EventInterface $event) + { + $params = $event->getParams(); + if ( + $params['context'] == 'search' + && isset($params['query']) + && $this->isLuceneReducible($params['query']) + ) { + $builder = $event->getTarget()->getQueryBuilder(); + $currentHelper = $builder->getLuceneHelper(); + $newHelper = new LuceneSyntaxHelperOverride($currentHelper); + $builder->setLuceneHelper($newHelper); + } + } + + + /** + * Escapes colons in Queries or recursively in QueryGroups. + * This prevents queries from being interpreted as advanced queries in Lucene syntax. + * cf. \VuFindSearch\Backend\Solr\LuceneSyntaxHelper::containsAdvancedLuceneSyntax + * + * @param Query|QueryGroup $queryOrGroup + * + * @return mixed + */ + private function isLuceneReducible($queryOrGroup) + { + if ($queryOrGroup instanceof QueryGroup) { + $handler = $queryOrGroup->getReducedHandler(); + if (is_null($handler) || isset($this->override_advanced_lucene_detection[$handler])) { + foreach ($queryOrGroup->getQueries() as $query) { + // cycle through all recursive subqueries, if any of these matches + // the whole query should not be checked for lucene syntax + if ($this->isLuceneReducible($query)) { + return true; + } + } + } + } elseif (isset($this->override_advanced_lucene_detection[$queryOrGroup->getHandler()])) { + return true; + } + return false; + } } diff --git a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js new file mode 100644 index 0000000000000000000000000000000000000000..e10770a83219ba52528d475215ee4a0c8b96d175 --- /dev/null +++ b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js @@ -0,0 +1,4 @@ +/*! bootstrap-accessibility-plugin - v1.0.6 - 2020-05-07 +* https://github.com/paypal/bootstrap-accessibility-plugin +* Copyright (c) 2020 PayPal Accessibility Team; Licensed BSD */ +!function($){"use strict";var uniqueId=function(prefix){return(prefix||"ui-id")+"-"+Math.floor(1e3*Math.random()+1)},focusable=function(element,isTabIndexNotNaN){var map,mapName,img,nodeName=element.nodeName.toLowerCase();return"area"===nodeName?(map=element.parentNode,mapName=map.name,element.href&&mapName&&"map"===map.nodeName.toLowerCase()?(img=$("img[usemap='#"+mapName+"']")[0],!!img&&visible(img)):!1):(/input|select|textarea|button|object/.test(nodeName)?!element.disabled:"a"===nodeName?element.href||isTabIndexNotNaN:isTabIndexNotNaN)&&visible(element)},visible=function(element){return $.expr.filters.visible(element)&&!$(element).parents().addBack().filter(function(){return"hidden"===$.css(this,"visibility")}).length};$.extend($.expr[":"],{data:$.expr.createPseudo?$.expr.createPseudo(function(dataName){return function(elem){return!!$.data(elem,dataName)}}):function(elem,i,match){return!!$.data(elem,match[3])},focusable:function(element){return focusable(element,!isNaN($.attr(element,"tabindex")))},tabbable:function(element){var tabIndex=$.attr(element,"tabindex"),isTabIndexNaN=isNaN(tabIndex);return(isTabIndexNaN||tabIndex>=0)&&focusable(element,!isTabIndexNaN)}}),$(".modal-dialog").attr({role:"document"});var modalhide=$.fn.modal.Constructor.prototype.hide;$.fn.modal.Constructor.prototype.hide=function(){modalhide.apply(this,arguments),$(document).off("keydown.bs.modal")};var modalfocus=$.fn.modal.Constructor.prototype.enforceFocus;$.fn.modal.Constructor.prototype.enforceFocus=function(){var $content=this.$element.find(".modal-content"),focEls=$content.find(":tabbable"),$lastEl=$(focEls[focEls.length-1]),$firstEl=$(focEls[0]);$lastEl.on("keydown.bs.modal",$.proxy(function(ev){9!==ev.keyCode||ev.shiftKey|ev.ctrlKey|ev.metaKey|ev.altKey||(ev.preventDefault(),$firstEl.focus())},this)),$firstEl.on("keydown.bs.modal",$.proxy(function(ev){9===ev.keyCode&&ev.shiftKey&&(ev.preventDefault(),$lastEl.focus())},this)),modalfocus.apply(this,arguments)};var $par,firstItem,toggle="[data-toggle=dropdown]",focusDelay=200,menus=$(toggle).parent().find("ul").attr("role","menu"),lis=menus.find("li").attr("role","presentation");lis.find("a").attr({role:"menuitem",tabIndex:"-1"}),$(toggle).attr({"aria-haspopup":"true","aria-expanded":"false"}),$(toggle).parent().on("shown.bs.dropdown",function(e){$par=$(this);var $toggle=$par.find(toggle);$toggle.attr("aria-expanded","true"),$toggle.on("keydown.bs.dropdown",$.proxy(function(ev){setTimeout(function(){firstItem=$(".dropdown-menu [role=menuitem]:visible",$par)[0];try{firstItem.focus()}catch(ex){}},focusDelay)},this))}).on("hidden.bs.dropdown",function(e){$par=$(this);var $toggle=$par.find(toggle);$toggle.attr("aria-expanded","false")}),$(document).on("focusout.dropdown.data-api",".dropdown-menu",function(e){var $this=$(this),that=this;$this.parent().hasClass("open")&&setTimeout(function(){$.contains(that,document.activeElement)||$this.parent().find("[data-toggle=dropdown]").dropdown("toggle")},150)}).on("keydown.bs.dropdown.data-api",toggle+", [role=menu]",$.fn.dropdown.Constructor.prototype.keydown);var $tablist=$(".nav-tabs, .nav-pills"),$lis=$tablist.children("li"),$tabs=$tablist.find('[data-toggle="tab"], [data-toggle="pill"]');$tabs&&($tablist.attr("role","tablist"),$lis.attr("role","presentation"),$tabs.attr("role","tab")),$tabs.each(function(index){var tabpanel=$($(this).attr("href")),tab=$(this),tabid=tab.attr("id")||uniqueId("ui-tab");tab.attr("id",tabid),tab.parent().hasClass("active")?(tab.attr({tabIndex:"0","aria-selected":"true","aria-controls":tab.attr("href").substr(1)}),tabpanel.attr({role:"tabpanel",tabIndex:"0","aria-hidden":"false","aria-labelledby":tabid})):(tab.attr({tabIndex:"-1","aria-selected":"false","aria-controls":tab.attr("href").substr(1)}),tabpanel.attr({role:"tabpanel",tabIndex:"-1","aria-hidden":"true","aria-labelledby":tabid}))}),$.fn.tab.Constructor.prototype.keydown=function(e){var $items,index,$this=$(this),$ul=$this.closest("ul[role=tablist] "),k=e.which||e.keyCode;if($this=$(this),/(37|38|39|40)/.test(k)){$items=$ul.find("[role=tab]:visible"),index=$items.index($items.filter(":focus")),(38==k||37==k)&&index--,(39==k||40==k)&&index++,0>index&&(index=$items.length-1),index==$items.length&&(index=0);var nextTab=$items.eq(index);"tab"===nextTab.attr("role")&&nextTab.tab("show").focus(),e.preventDefault(),e.stopPropagation()}},$(document).on("keydown.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',$.fn.tab.Constructor.prototype.keydown);var tabactivate=$.fn.tab.Constructor.prototype.activate;$.fn.tab.Constructor.prototype.activate=function(element,container,callback){var $active=container.find("> .active");$active.find("[data-toggle=tab], [data-toggle=pill]").attr({tabIndex:"-1","aria-selected":!1}),$active.filter(".tab-pane").attr({"aria-hidden":!0,tabIndex:"-1"}),tabactivate.apply(this,arguments),element.addClass("active"),element.find("[data-toggle=tab], [data-toggle=pill]").attr({tabIndex:"0","aria-selected":!0}),element.filter(".tab-pane").attr({"aria-hidden":!1,tabIndex:"0"})};var $colltabs=$('[data-toggle="collapse"]');$colltabs.each(function(index){var colltab=$(this),collpanel=$(colltab.attr("data-target")?colltab.attr("data-target"):colltab.attr("href")),parent=colltab.attr("data-parent"),collparent=parent&&$(parent),collid=colltab.attr("id")||uniqueId("ui-collapse"),parentpanel=collpanel.parent(),parentfirstchild=collparent?collparent.find(".panel.panel-default:first-child"):null,hasopenpanel=collparent?collparent.find(".panel-collapse.in").length>0:!1;colltab.attr("id",collid),collparent&&(colltab.attr({"aria-controls":collpanel.attr("id"),role:"tab","aria-selected":"false","aria-expanded":"false"}),$(collparent).find("div:not(.collapse,.panel-body), h4").attr("role","presentation"),collparent.attr({role:"tablist","aria-multiselectable":"true"}),collpanel.attr({role:"tabpanel","aria-labelledby":collid}),!hasopenpanel&&parentpanel.is(parentfirstchild)?(colltab.attr({tabindex:"0"}),collpanel.attr({tabindex:"-1"})):collpanel.hasClass("in")?(colltab.attr({"aria-selected":"true","aria-expanded":"true",tabindex:"0"}),collpanel.attr({tabindex:"0","aria-hidden":"false"})):(colltab.attr({tabindex:"-1"}),collpanel.attr({tabindex:"-1","aria-hidden":"true"})))});var collToggle=$.fn.collapse.Constructor.prototype.toggle;$.fn.collapse.Constructor.prototype.toggle=function(){var href,prevTab=this.$parent&&this.$parent.find('[aria-expanded="true"]');if(prevTab){var curTab,prevPanel=prevTab.attr("data-target")||(href=prevTab.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),$prevPanel=$(prevPanel),$curPanel=this.$element;this.$parent;this.$parent&&(curTab=this.$parent.find('[data-toggle=collapse][href="#'+this.$element.attr("id")+'"]')),collToggle.apply(this,arguments),$.support.transition&&this.$element.one($.support.transition.end,function(){prevTab.attr({"aria-selected":"false","aria-expanded":"false",tabIndex:"-1"}),$prevPanel.attr({"aria-hidden":"true",tabIndex:"-1"}),curTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:"0"}),$curPanel.hasClass("in")?$curPanel.attr({"aria-hidden":"false",tabIndex:"0"}):(curTab.attr({"aria-selected":"false","aria-expanded":"false"}),$curPanel.attr({"aria-hidden":"true",tabIndex:"-1"}))})}else collToggle.apply(this,arguments)},$.fn.collapse.Constructor.prototype.keydown=function(e){var $items,index,$this=$(this),$tablist=$this.closest("div[role=tablist] "),k=e.which||e.keyCode;$this=$(this),/(32|37|38|39|40)/.test(k)&&(32==k&&$this.click(),$items=$tablist.find("[role=tab]"),index=$items.index($items.filter(":focus")),(38==k||37==k)&&index--,(39==k||40==k)&&index++,0>index&&(index=$items.length-1),index==$items.length&&(index=0),$items.eq(index).focus(),e.preventDefault(),e.stopPropagation())},$(document).on("keydown.collapse.data-api",'[data-toggle="collapse"]',$.fn.collapse.Constructor.prototype.keydown),$(".carousel").each(function(index){function setTablistHighlightBox(){var $tab,offset,height,width,highlightBox={};highlightBox.top=0,highlightBox.left=32e3,highlightBox.height=0,highlightBox.width=0;for(var i=0;i<$tabs.length;i++){$tab=$tabs[i],offset=$($tab).offset(),height=$($tab).height(),width=$($tab).width(),highlightBox.top<offset.top&&(highlightBox.top=Math.round(offset.top)),highlightBox.height<height&&(highlightBox.height=Math.round(height)),highlightBox.left>offset.left&&(highlightBox.left=Math.round(offset.left));var w=offset.left-highlightBox.left+Math.round(width);highlightBox.width<w&&(highlightBox.width=w)}$tablistHighlight.style.top=highlightBox.top-2+"px",$tablistHighlight.style.left=highlightBox.left-2+"px",$tablistHighlight.style.height=highlightBox.height+7+"px",$tablistHighlight.style.width=highlightBox.width+8+"px"}var $tabpanel,$tablistHighlight,$pauseCarousel,$complementaryLandmark,$tab,i,$this=$(this),$prev=$this.find('[data-slide="prev"]'),$next=$this.find('[data-slide="next"]'),$tablist=$this.find(".carousel-indicators"),$tabs=$this.find(".carousel-indicators li"),$tabpanels=$this.find(".item"),$is_paused=!1,id_title="id_title",id_desc="id_desc";for($tablist.attr("role","tablist"),$tabs.focus(function(){$this.carousel("pause"),$is_paused=!0,$pauseCarousel.innerHTML="Bilderkarussel starten",$(this).parent().addClass("active"),setTablistHighlightBox(),$($tablistHighlight).addClass("focus"),$(this).parents(".carousel").addClass("contrast")}),$tabs.blur(function(event){$(this).parent().removeClass("active"),$($tablistHighlight).removeClass("focus"),$(this).parents(".carousel").removeClass("contrast")}),i=0;i<$tabpanels.length;i++)$tabpanel=$tabpanels[i],$tabpanel.setAttribute("role","tabpanel"),$tabpanel.setAttribute("id","tabpanel-"+index+"-"+i),$tabpanel.setAttribute("aria-labelledby","tab-"+index+"-"+i);for("string"!=typeof $this.attr("role")&&($this.attr("role","complementary"),$this.attr("aria-labelledby",id_title),$this.attr("aria-describedby",id_desc),$this.prepend('<p id="'+id_desc+'" class="sr-only">Dieser Bilderkarussell können Sie über Tastatur oder Maus steuern. Mit den Tabs bzw. den Vorher- und Nachher-Schaltflächen können Sie zwischen den Bildern wechseln.</p>'),$this.prepend('<h2 id="'+id_title+'" class="sr-only">Bilderkarussel mit '+$tabpanels.length+" Bildern.</h2>")),i=0;i<$tabs.length;i++){$tab=$tabs[i],$tab.setAttribute("role","tab"),$tab.setAttribute("id","tab-"+index+"-"+i),$tab.setAttribute("aria-controls","tabpanel-"+index+"-"+i);var tpId="#tabpanel-"+index+"-"+i,caption=$this.find(tpId).find("h1").text();("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h3").text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h4").text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h5").text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h6").text()),("string"!=typeof caption||0===caption.length)&&(caption="no title");var tabName=document.createElement("span");tabName.setAttribute("class","sr-only"),tabName.innerHTML="Slide "+(i+1),caption&&(tabName.innerHTML+=": "+caption),$tab.appendChild(tabName)}$tablistHighlight=document.createElement("div"),$tablistHighlight.className="carousel-tablist-highlight",document.body.appendChild($tablistHighlight),$complementaryLandmark=document.createElement("aside"),$complementaryLandmark.setAttribute("class","carousel-aside-pause"),$complementaryLandmark.setAttribute("aria-label","Stopp- und Startsteuerung für Bildkarussel"),$this.prepend($complementaryLandmark),$pauseCarousel=document.createElement("button"),$pauseCarousel.className="carousel-pause-button",$pauseCarousel.innerHTML="Bilderkarussel stoppen",$pauseCarousel.setAttribute("title","Sie können diese Schaltfläche nutzen, um die Karussellanimationen zu stoppen."),$($complementaryLandmark).append($pauseCarousel),$($pauseCarousel).click(function(){$is_paused?($pauseCarousel.innerHTML="Karussel anhalten",$this.carousel("cycle"),$is_paused=!1):($pauseCarousel.innerHTML="Bilderkarussel starten",$this.carousel("pause"),$is_paused=!0)}),$($pauseCarousel).focus(function(){$(this).addClass("focus")}),$($pauseCarousel).blur(function(){$(this).removeClass("focus")}),setTablistHighlightBox(),$(window).resize(function(){setTablistHighlightBox()}),$prev.attr("aria-label","Vorheriges Bild"),$prev.keydown(function(e){var k=e.which||e.keyCode;/(13|32)/.test(k)&&(e.preventDefault(),e.stopPropagation(),$prev.trigger("click"))}),$prev.focus(function(){$(this).parents(".carousel").addClass("contrast")}),$prev.blur(function(){$(this).parents(".carousel").removeClass("contrast")}),$next.attr("aria-label","Nächstes Bild"),$next.keydown(function(e){var k=e.which||e.keyCode;/(13|32)/.test(k)&&(e.preventDefault(),e.stopPropagation(),$next.trigger("click"))}),$next.focus(function(){$(this).parents(".carousel").addClass("contrast")}),$next.blur(function(){$(this).parents(".carousel").removeClass("contrast")}),$(".carousel-inner a").focus(function(){$(this).parents(".carousel").addClass("contrast")}),$(".carousel-inner a").blur(function(){$(this).parents(".carousel").removeClass("contrast")}),$tabs.each(function(){var item=$(this);item.hasClass("active")?item.attr({"aria-selected":"true",tabindex:"0"}):item.attr({"aria-selected":"false",tabindex:"-1"})})});var slideCarousel=$.fn.carousel.Constructor.prototype.slide;$.fn.carousel.Constructor.prototype.slide=function(type,next){var $id,$element=this.$element,$active=$element.find("[role=tabpanel].active"),$next=next||$active[type](),$tab_count=$element.find("[role=tabpanel]").length,$prev_side=$element.find('[data-slide="prev"]'),$next_side=$element.find('[data-slide="next"]'),$index=0,$prev_index=$tab_count-1,$next_index=1;$next&&$next.attr("id")&&($id=$next.attr("id"),$index=$id.lastIndexOf("-"),$index>=0&&($index=parseInt($id.substring($index+1),10)),$prev_index=$index-1,1>$prev_index&&($prev_index=$tab_count-1),$next_index=$index+1,$next_index>=$tab_count&&($next_index=0)),$prev_side.attr("aria-label","Zeige Bild "+($prev_index+1)+" von "+$tab_count),$next_side.attr("aria-label","Zeige Bild "+($next_index+1)+" von "+$tab_count),slideCarousel.apply(this,arguments),$active.one("bsTransitionEnd",function(){var $tab;$tab=$element.find('li[aria-controls="'+$active.attr("id")+'"]'),$tab&&$tab.attr({"aria-selected":!1,tabIndex:"-1"}),$tab=$element.find('li[aria-controls="'+$next.attr("id")+'"]'),$tab&&$tab.attr({"aria-selected":!0,tabIndex:"0"})})};var $this;$.fn.carousel.Constructor.prototype.keydown=function(e){function selectTab(index){index>=$tabs.length||0>index||($carousel.carousel(index),setTimeout(function(){$tabs[index].focus()},150))}$this=$this||$(this),this instanceof Node&&($this=$(this));var index,$carousel=$(e.target).closest(".carousel"),$tabs=$carousel.find("[role=tab]"),k=e.which||e.keyCode;/(37|38|39|40)/.test(k)&&(index=$tabs.index($tabs.filter(".active")),(37==k||38==k)&&(index--,selectTab(index)),(39==k||40==k)&&(index++,selectTab(index)),e.preventDefault(),e.stopPropagation())},$(document).on("keydown.carousel.data-api","li[role=tab]",$.fn.carousel.Constructor.prototype.keydown)}(jQuery); \ No newline at end of file diff --git a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js new file mode 100644 index 0000000000000000000000000000000000000000..423c963b4fd7b5fe345689b89a3c94233085d8e4 --- /dev/null +++ b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js @@ -0,0 +1,4 @@ +/*! bootstrap-accessibility-plugin - v1.0.6 - 2020-05-07 +* https://github.com/paypal/bootstrap-accessibility-plugin +* Copyright (c) 2020 PayPal Accessibility Team; Licensed BSD */ +!function($){"use strict";console.log('en');var uniqueId=function(prefix){return(prefix||"ui-id")+"-"+Math.floor(1e3*Math.random()+1)},focusable=function(element,isTabIndexNotNaN){var map,mapName,img,nodeName=element.nodeName.toLowerCase();return"area"===nodeName?(map=element.parentNode,mapName=map.name,element.href&&mapName&&"map"===map.nodeName.toLowerCase()?(img=$("img[usemap='#"+mapName+"']")[0],!!img&&visible(img)):!1):(/input|select|textarea|button|object/.test(nodeName)?!element.disabled:"a"===nodeName?element.href||isTabIndexNotNaN:isTabIndexNotNaN)&&visible(element)},visible=function(element){return $.expr.filters.visible(element)&&!$(element).parents().addBack().filter(function(){return"hidden"===$.css(this,"visibility")}).length};$.extend($.expr[":"],{data:$.expr.createPseudo?$.expr.createPseudo(function(dataName){return function(elem){return!!$.data(elem,dataName)}}):function(elem,i,match){return!!$.data(elem,match[3])},focusable:function(element){return focusable(element,!isNaN($.attr(element,"tabindex")))},tabbable:function(element){var tabIndex=$.attr(element,"tabindex"),isTabIndexNaN=isNaN(tabIndex);return(isTabIndexNaN||tabIndex>=0)&&focusable(element,!isTabIndexNaN)}}),$(".modal-dialog").attr({role:"document"});var modalhide=$.fn.modal.Constructor.prototype.hide;$.fn.modal.Constructor.prototype.hide=function(){modalhide.apply(this,arguments),$(document).off("keydown.bs.modal")};var modalfocus=$.fn.modal.Constructor.prototype.enforceFocus;$.fn.modal.Constructor.prototype.enforceFocus=function(){var $content=this.$element.find(".modal-content"),focEls=$content.find(":tabbable"),$lastEl=$(focEls[focEls.length-1]),$firstEl=$(focEls[0]);$lastEl.on("keydown.bs.modal",$.proxy(function(ev){9!==ev.keyCode||ev.shiftKey|ev.ctrlKey|ev.metaKey|ev.altKey||(ev.preventDefault(),$firstEl.focus())},this)),$firstEl.on("keydown.bs.modal",$.proxy(function(ev){9===ev.keyCode&&ev.shiftKey&&(ev.preventDefault(),$lastEl.focus())},this)),modalfocus.apply(this,arguments)};var $par,firstItem,toggle="[data-toggle=dropdown]",focusDelay=200,menus=$(toggle).parent().find("ul").attr("role","menu"),lis=menus.find("li").attr("role","presentation");lis.find("a").attr({role:"menuitem",tabIndex:"-1"}),$(toggle).attr({"aria-haspopup":"true","aria-expanded":"false"}),$(toggle).parent().on("shown.bs.dropdown",function(e){$par=$(this);var $toggle=$par.find(toggle);$toggle.attr("aria-expanded","true"),$toggle.on("keydown.bs.dropdown",$.proxy(function(ev){setTimeout(function(){firstItem=$(".dropdown-menu [role=menuitem]:visible",$par)[0];try{firstItem.focus()}catch(ex){}},focusDelay)},this))}).on("hidden.bs.dropdown",function(e){$par=$(this);var $toggle=$par.find(toggle);$toggle.attr("aria-expanded","false")}),$(document).on("focusout.dropdown.data-api",".dropdown-menu",function(e){var $this=$(this),that=this;$this.parent().hasClass("open")&&setTimeout(function(){$.contains(that,document.activeElement)||$this.parent().find("[data-toggle=dropdown]").dropdown("toggle")},150)}).on("keydown.bs.dropdown.data-api",toggle+", [role=menu]",$.fn.dropdown.Constructor.prototype.keydown);var $tablist=$(".nav-tabs, .nav-pills"),$lis=$tablist.children("li"),$tabs=$tablist.find('[data-toggle="tab"], [data-toggle="pill"]');$tabs&&($tablist.attr("role","tablist"),$lis.attr("role","presentation"),$tabs.attr("role","tab")),$tabs.each(function(index){var tabpanel=$($(this).attr("href")),tab=$(this),tabid=tab.attr("id")||uniqueId("ui-tab");tab.attr("id",tabid),tab.parent().hasClass("active")?(tab.attr({tabIndex:"0","aria-selected":"true","aria-controls":tab.attr("href").substr(1)}),tabpanel.attr({role:"tabpanel",tabIndex:"0","aria-hidden":"false","aria-labelledby":tabid})):(tab.attr({tabIndex:"-1","aria-selected":"false","aria-controls":tab.attr("href").substr(1)}),tabpanel.attr({role:"tabpanel",tabIndex:"-1","aria-hidden":"true","aria-labelledby":tabid}))}),$.fn.tab.Constructor.prototype.keydown=function(e){var $items,index,$this=$(this),$ul=$this.closest("ul[role=tablist] "),k=e.which||e.keyCode;if($this=$(this),/(37|38|39|40)/.test(k)){$items=$ul.find("[role=tab]:visible"),index=$items.index($items.filter(":focus")),(38==k||37==k)&&index--,(39==k||40==k)&&index++,0>index&&(index=$items.length-1),index==$items.length&&(index=0);var nextTab=$items.eq(index);"tab"===nextTab.attr("role")&&nextTab.tab("show").focus(),e.preventDefault(),e.stopPropagation()}},$(document).on("keydown.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',$.fn.tab.Constructor.prototype.keydown);var tabactivate=$.fn.tab.Constructor.prototype.activate;$.fn.tab.Constructor.prototype.activate=function(element,container,callback){var $active=container.find("> .active");$active.find("[data-toggle=tab], [data-toggle=pill]").attr({tabIndex:"-1","aria-selected":!1}),$active.filter(".tab-pane").attr({"aria-hidden":!0,tabIndex:"-1"}),tabactivate.apply(this,arguments),element.addClass("active"),element.find("[data-toggle=tab], [data-toggle=pill]").attr({tabIndex:"0","aria-selected":!0}),element.filter(".tab-pane").attr({"aria-hidden":!1,tabIndex:"0"})};var $colltabs=$('[data-toggle="collapse"]');$colltabs.each(function(index){var colltab=$(this),collpanel=$(colltab.attr("data-target")?colltab.attr("data-target"):colltab.attr("href")),parent=colltab.attr("data-parent"),collparent=parent&&$(parent),collid=colltab.attr("id")||uniqueId("ui-collapse"),parentpanel=collpanel.parent(),parentfirstchild=collparent?collparent.find(".panel.panel-default:first-child"):null,hasopenpanel=collparent?collparent.find(".panel-collapse.in").length>0:!1;colltab.attr("id",collid),collparent&&(colltab.attr({"aria-controls":collpanel.attr("id"),role:"tab","aria-selected":"false","aria-expanded":"false"}),$(collparent).find("div:not(.collapse,.panel-body), h4").attr("role","presentation"),collparent.attr({role:"tablist","aria-multiselectable":"true"}),collpanel.attr({role:"tabpanel","aria-labelledby":collid}),!hasopenpanel&&parentpanel.is(parentfirstchild)?(colltab.attr({tabindex:"0"}),collpanel.attr({tabindex:"-1"})):collpanel.hasClass("in")?(colltab.attr({"aria-selected":"true","aria-expanded":"true",tabindex:"0"}),collpanel.attr({tabindex:"0","aria-hidden":"false"})):(colltab.attr({tabindex:"-1"}),collpanel.attr({tabindex:"-1","aria-hidden":"true"})))});var collToggle=$.fn.collapse.Constructor.prototype.toggle;$.fn.collapse.Constructor.prototype.toggle=function(){var href,prevTab=this.$parent&&this.$parent.find('[aria-expanded="true"]');if(prevTab){var curTab,prevPanel=prevTab.attr("data-target")||(href=prevTab.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),$prevPanel=$(prevPanel),$curPanel=this.$element;this.$parent;this.$parent&&(curTab=this.$parent.find('[data-toggle=collapse][href="#'+this.$element.attr("id")+'"]')),collToggle.apply(this,arguments),$.support.transition&&this.$element.one($.support.transition.end,function(){prevTab.attr({"aria-selected":"false","aria-expanded":"false",tabIndex:"-1"}),$prevPanel.attr({"aria-hidden":"true",tabIndex:"-1"}),curTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:"0"}),$curPanel.hasClass("in")?$curPanel.attr({"aria-hidden":"false",tabIndex:"0"}):(curTab.attr({"aria-selected":"false","aria-expanded":"false"}),$curPanel.attr({"aria-hidden":"true",tabIndex:"-1"}))})}else collToggle.apply(this,arguments)},$.fn.collapse.Constructor.prototype.keydown=function(e){var $items,index,$this=$(this),$tablist=$this.closest("div[role=tablist] "),k=e.which||e.keyCode;$this=$(this),/(32|37|38|39|40)/.test(k)&&(32==k&&$this.click(),$items=$tablist.find("[role=tab]"),index=$items.index($items.filter(":focus")),(38==k||37==k)&&index--,(39==k||40==k)&&index++,0>index&&(index=$items.length-1),index==$items.length&&(index=0),$items.eq(index).focus(),e.preventDefault(),e.stopPropagation())},$(document).on("keydown.collapse.data-api",'[data-toggle="collapse"]',$.fn.collapse.Constructor.prototype.keydown),$(".carousel").each(function(index){function setTablistHighlightBox(){var $tab,offset,height,width,highlightBox={};highlightBox.top=0,highlightBox.left=32e3,highlightBox.height=0,highlightBox.width=0;for(var i=0;i<$tabs.length;i++){$tab=$tabs[i],offset=$($tab).offset(),height=$($tab).height(),width=$($tab).width(),highlightBox.top<offset.top&&(highlightBox.top=Math.round(offset.top)),highlightBox.height<height&&(highlightBox.height=Math.round(height)),highlightBox.left>offset.left&&(highlightBox.left=Math.round(offset.left));var w=offset.left-highlightBox.left+Math.round(width);highlightBox.width<w&&(highlightBox.width=w)}$tablistHighlight.style.top=highlightBox.top-2+"px",$tablistHighlight.style.left=highlightBox.left-2+"px",$tablistHighlight.style.height=highlightBox.height+7+"px",$tablistHighlight.style.width=highlightBox.width+8+"px"}var $tabpanel,$tablistHighlight,$pauseCarousel,$complementaryLandmark,$tab,i,$this=$(this),$prev=$this.find('[data-slide="prev"]'),$next=$this.find('[data-slide="next"]'),$tablist=$this.find(".carousel-indicators"),$tabs=$this.find(".carousel-indicators li"),$tabpanels=$this.find(".item"),$is_paused=!1,id_title="id_title",id_desc="id_desc";for($tablist.attr("role","tablist"),$tabs.focus(function(){$this.carousel("pause"),$is_paused=!0,$pauseCarousel.innerHTML="Start Image Carousel",$(this).parent().addClass("active"),setTablistHighlightBox(),$($tablistHighlight).addClass("focus"),$(this).parents(".carousel").addClass("contrast")}),$tabs.blur(function(event){$(this).parent().removeClass("active"),$($tablistHighlight).removeClass("focus"),$(this).parents(".carousel").removeClass("contrast")}),i=0;i<$tabpanels.length;i++)$tabpanel=$tabpanels[i],$tabpanel.setAttribute("role","tabpanel"),$tabpanel.setAttribute("id","tabpanel-"+index+"-"+i),$tabpanel.setAttribute("aria-labelledby","tab-"+index+"-"+i);for("string"!=typeof $this.attr("role")&&($this.attr("role","complementary"),$this.attr("aria-labelledby",id_title),$this.attr("aria-describedby",id_desc),$this.prepend('<p id="'+id_desc+'" class="sr-only">You can control this image carousel using your keyboard or pointing device. Use the tabs or the previous and next buttons to change the image displayed.</p>'),$this.prepend('<h2 id="'+id_title+'" class="sr-only">Carousel with '+$tabpanels.length+" images.</h2>")),i=0;i<$tabs.length;i++){$tab=$tabs[i],$tab.setAttribute("role","tab"),$tab.setAttribute("id","tab-"+index+"-"+i),$tab.setAttribute("aria-controls","tabpanel-"+index+"-"+i);var tpId="#tabpanel-"+index+"-"+i,caption=$this.find(tpId).find("h1").text();("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h3").text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h4").text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h5").text()),("string"!=typeof caption||0===caption.length)&&(caption=$this.find(tpId).find("h6").text()),("string"!=typeof caption||0===caption.length)&&(caption="no title");var tabName=document.createElement("span");tabName.setAttribute("class","sr-only"),tabName.innerHTML="Slide "+(i+1),caption&&(tabName.innerHTML+=": "+caption),$tab.appendChild(tabName)}$tablistHighlight=document.createElement("div"),$tablistHighlight.className="carousel-tablist-highlight",document.body.appendChild($tablistHighlight),$complementaryLandmark=document.createElement("aside"),$complementaryLandmark.setAttribute("class","carousel-aside-pause"),$complementaryLandmark.setAttribute("aria-label","Carousel pause/start control"),$this.prepend($complementaryLandmark),$pauseCarousel=document.createElement("button"),$pauseCarousel.className="carousel-pause-button",$pauseCarousel.innerHTML="Pause Carousel",$pauseCarousel.setAttribute("title","Use the pause/start carousel button to stop/start carousel animations."),$($complementaryLandmark).append($pauseCarousel),$($pauseCarousel).click(function(){$is_paused?($pauseCarousel.innerHTML="Pause Carousel",$this.carousel("cycle"),$is_paused=!1):($pauseCarousel.innerHTML="Start Carousel",$this.carousel("pause"),$is_paused=!0)}),$($pauseCarousel).focus(function(){$(this).addClass("focus")}),$($pauseCarousel).blur(function(){$(this).removeClass("focus")}),setTablistHighlightBox(),$(window).resize(function(){setTablistHighlightBox()}),$prev.attr("aria-label","Previous image"),$prev.keydown(function(e){var k=e.which||e.keyCode;/(13|32)/.test(k)&&(e.preventDefault(),e.stopPropagation(),$prev.trigger("click"))}),$prev.focus(function(){$(this).parents(".carousel").addClass("contrast")}),$prev.blur(function(){$(this).parents(".carousel").removeClass("contrast")}),$next.attr("aria-label","Next Slide"),$next.keydown(function(e){var k=e.which||e.keyCode;/(13|32)/.test(k)&&(e.preventDefault(),e.stopPropagation(),$next.trigger("click"))}),$next.focus(function(){$(this).parents(".carousel").addClass("contrast")}),$next.blur(function(){$(this).parents(".carousel").removeClass("contrast")}),$(".carousel-inner a").focus(function(){$(this).parents(".carousel").addClass("contrast")}),$(".carousel-inner a").blur(function(){$(this).parents(".carousel").removeClass("contrast")}),$tabs.each(function(){var item=$(this);item.hasClass("active")?item.attr({"aria-selected":"true",tabindex:"0"}):item.attr({"aria-selected":"false",tabindex:"-1"})})});var slideCarousel=$.fn.carousel.Constructor.prototype.slide;$.fn.carousel.Constructor.prototype.slide=function(type,next){var $id,$element=this.$element,$active=$element.find("[role=tabpanel].active"),$next=next||$active[type](),$tab_count=$element.find("[role=tabpanel]").length,$prev_side=$element.find('[data-slide="prev"]'),$next_side=$element.find('[data-slide="next"]'),$index=0,$prev_index=$tab_count-1,$next_index=1;$next&&$next.attr("id")&&($id=$next.attr("id"),$index=$id.lastIndexOf("-"),$index>=0&&($index=parseInt($id.substring($index+1),10)),$prev_index=$index-1,1>$prev_index&&($prev_index=$tab_count-1),$next_index=$index+1,$next_index>=$tab_count&&($next_index=0)),$prev_side.attr("aria-label","Show image "+($prev_index+1)+" of "+$tab_count),$next_side.attr("aria-label","Show image "+($next_index+1)+" of "+$tab_count),slideCarousel.apply(this,arguments),$active.one("bsTransitionEnd",function(){var $tab;$tab=$element.find('li[aria-controls="'+$active.attr("id")+'"]'),$tab&&$tab.attr({"aria-selected":!1,tabIndex:"-1"}),$tab=$element.find('li[aria-controls="'+$next.attr("id")+'"]'),$tab&&$tab.attr({"aria-selected":!0,tabIndex:"0"})})};var $this;$.fn.carousel.Constructor.prototype.keydown=function(e){function selectTab(index){index>=$tabs.length||0>index||($carousel.carousel(index),setTimeout(function(){$tabs[index].focus()},150))}$this=$this||$(this),this instanceof Node&&($this=$(this));var index,$carousel=$(e.target).closest(".carousel"),$tabs=$carousel.find("[role=tab]"),k=e.which||e.keyCode;/(37|38|39|40)/.test(k)&&(index=$tabs.index($tabs.filter(".active")),(37==k||38==k)&&(index--,selectTab(index)),(39==k||40==k)&&(index++,selectTab(index)),e.preventDefault(),e.stopPropagation())},$(document).on("keydown.carousel.data-api","li[role=tab]",$.fn.carousel.Constructor.prototype.keydown)}(jQuery); \ No newline at end of file diff --git a/themes/finc-accessibility/js/vendor/bootstrap-accessibility.min.js b/themes/finc-accessibility/js/vendor/bootstrap-accessibility.min.js new file mode 100644 index 0000000000000000000000000000000000000000..87ceae0771270dc50adf5f4ac212c3df8280bdd5 --- /dev/null +++ b/themes/finc-accessibility/js/vendor/bootstrap-accessibility.min.js @@ -0,0 +1 @@ +/*! override bootstrap - use specific language versions */ \ No newline at end of file diff --git a/themes/finc-accessibility/templates/RecordTab/similaritemscarousel.phtml b/themes/finc-accessibility/templates/RecordTab/similaritemscarousel.phtml new file mode 100644 index 0000000000000000000000000000000000000000..f112e618638e5d1d11e52ae77beee3bfc6380b46 --- /dev/null +++ b/themes/finc-accessibility/templates/RecordTab/similaritemscarousel.phtml @@ -0,0 +1,75 @@ +<?php /* add language specific translations for carousel */ ?> +<?php if (strcmp($this->layout()->userLang, 'de') == 0): ?> + <?=$this->inlineScript(\Zend\View\Helper\HeadScript::FILE, 'vendor/bootstrap-accessibility-de.min.js', 'SET');?> +<?php endif; ?> +<h2><?=$this->transEsc('Similar Items')?></h2> +<?php $similarRecords = $this->tab->getResults(); ?> +<?php if (!empty($similarRecords)): ?> + <?php $perPage = 4 ?> + <div id="similar-items-carousel" class="carousel slide" data-ride="carousel"> + <!-- Indicators --> + <ol class="carousel-indicators"> + <li data-target="#similar-items-carousel" data-slide-to="0" class="active"></li> + <?php for($i = 1;$i < count($similarRecords) / $perPage;$i++): ?> + <li data-target="#similar-items-carousel" data-slide-to="<?=$i ?>"></li> + <?php endfor; ?> + </ol> + + <!-- Wrapper for slides --> + <div class="carousel-inner"> + <div class="item active"> + <?php foreach ($similarRecords as $index => $data): ?> + <div class="carousel-item"> + <a class="hover-overlay" href="<?=$this->recordLink()->getUrl($data)?>"> + <?php $thumb = $this->record($data)->getThumbnail('large'); ?> + <img src="<?=$thumb ?>" title="<?=$data->getTitle() ?>"/> + <div class="content"> + <?php $formats = $data->getFormats(); ?> + <i class="fa fa-x<?php if (count($formats) > 0): ?> fa-<?=preg_replace('/[^a-z0-9]/', '', strtolower($formats[0]))?>" title="<?=$formats[0] ?><?php endif; ?>"></i> + <b><?=$this->escapeHtml($data->getTitle())?></b> + <?php $authors = $data->getPrimaryAuthors(); if (!empty($authors)): ?> + <br/><?=$this->transEsc('by')?>: <?=$this->escapeHtml($authors[0]);?><?php if (count($authors) > 1): ?>, <?=$this->transEsc('more_authors_abbrev')?><?php endif; ?> + <?php endif; ?> + <?php $pubDates = $data->getPublicationDates(); if (!empty($pubDates)): ?> + <br/><?=$this->transEsc('Published')?>: (<?=$this->escapeHtml($pubDates[0])?>) + <?php endif; ?> + </div> + </a> + </div> + <?php if(($index + 1) % $perPage == 0 && $index < count($similarRecords) - 1): ?> + </div> + <div class="item"> + <?php endif; ?> + <?php endforeach; ?> + </div> + </div> + + <!-- Controls --> + <a class="left carousel-control" href="#similar-items-carousel" role="button" data-slide="prev" aria-label="<?=$this->transEsc('Prev') ?>"> + <span class="fa fa-chevron-left glyphicon-chevron-left" title="<?=$this->transEsc('Prev') ?>"></span> + </a> + <a class="right carousel-control" href="#similar-items-carousel" role="button" data-slide="next" aria-label="<?=$this->transEsc('Next') ?>"> + <span class="fa fa-chevron-right glyphicon-chevron-right" title="<?=$this->transEsc('Next') ?>"></span> + </a> + </div> +<?php else: ?> + <p><?=$this->transEsc('Cannot find similar records')?></p> +<?php endif; ?> +<?php + $script = <<<JS +var normalizeHeightCount = $('#similar-items-carousel img').length; +function normalizeHeights() { + if(--normalizeHeightCount > 0) return; + var tallest = 0; + var items = $('#similar-items-carousel .hover-overlay'); + items.each(function() { //add heights to array + if(tallest < $(this).height()) { + tallest = $(this).height(); + } + }); + items.css('min-height', (tallest+25) + 'px'); +} +$('#similar-items-carousel img').load(normalizeHeights); +JS; +?> +<?=$this->inlineScript(\Zend\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> diff --git a/themes/finc/scss/_customMixins.scss b/themes/finc/scss/_customMixins.scss index b511a3e60b4bb88632453e18cca574ad0a7ff279..460b1cce5117d8a23c1ab8ccd142af2425add8ad 100644 --- a/themes/finc/scss/_customMixins.scss +++ b/themes/finc/scss/_customMixins.scss @@ -23,7 +23,7 @@ } // Outline Mixin -- complements BS outline mixin in partials/mixin but does not rewrite it -@mixin outline($size: $outline-default-size-finc, $color: $outline-default-color-finc, $style: $outline-default-style-finc) { +@mixin outline($size: $outline-default-size, $color: $outline-default-color, $style: $outline-default-style) { outline: $style $size $color; } diff --git a/themes/finc/scss/_customVariables.scss b/themes/finc/scss/_customVariables.scss index 29bdd7bb4c4dd89e9cf6d91289b3e4d5242021a1..24e6a91e497583bba2a0a5a8b7c307d04615d495 100644 --- a/themes/finc/scss/_customVariables.scss +++ b/themes/finc/scss/_customVariables.scss @@ -115,10 +115,10 @@ $border-default-white: 1px solid $white !default; $border-right-width: 1px !default; $margin-right-width: inherit !default; -//// Outlines (focus, accessibility etc.) -- complements similar BS values -$outline-default-color-finc: $black !default; -$outline-default-size-finc: 1px !default; -$outline-default-style-finc: solid !default; +//// Outlines (focus, accessibility etc.) -- overriding default bootstrap accessibility plugin scss variables +$outline-default-color: $black !default; +$outline-default-size: 1px !default; +$outline-default-style: solid !default; //// PAGER $pagination-disabled-color: $gainsboro !default; diff --git a/themes/finc/scss/compiled.scss b/themes/finc/scss/compiled.scss index f225ac9b883d6d17fb55761d494c0673d47fdc47..a7fbd375f82075f348503611d72770e940b5b340 100644 --- a/themes/finc/scss/compiled.scss +++ b/themes/finc/scss/compiled.scss @@ -191,6 +191,13 @@ h2 { //// h3 h3 { font-size: 1.5rem; + + // H3 in MyAccount needs to be pushed in + #myresearch-sidebar & { + @media screen and (max-width: $screen-xs-max) { + padding-left: $half-gutter; + } + } } //// h4 @@ -2079,16 +2086,16 @@ footer { .search-filter-toggle::before { content: '\f100\00a0'; } -} -.offcanvas-left .record { - .media-left.img-col { - // push record view icon to right (Desktop) on left-aligned sidebars -- pls NOTE: required offcanvas = true - @media (min-width: $screen-sm-min) { - float: right; - margin-left: $grid-gutter-width / 2; - margin-right: 0; - padding-right: 0; + .record { + .media-left.img-col { + // push record view icon to right (Desktop) on left-aligned sidebars -- pls NOTE: required offcanvas = true + @media (min-width: $screen-sm-min) { + float: right; + margin-left: $grid-gutter-width / 2; + margin-right: 0; + padding-right: 0; + } } } } @@ -2708,11 +2715,18 @@ body:not(.offcanvas) .sidebar { //// Style myaccount sidebar menues to get the same look as facets .myresearch-menu { - border: 1px solid $border-color; - a:not(:last-of-type) { - border-bottom: 1px solid $border-color; - padding: $sidebar-item-padding; // overwrite values from BS with more sensible values + a { + width: 100%; + + &:not(:last-of-type) { + border-bottom: 1px solid $border-color; + padding: $sidebar-item-padding; // overwrite values from BS with more sensible values + } + } + + .facet { + padding: 0 !important; } } @@ -2738,6 +2752,11 @@ body:not(.offcanvas) .sidebar { } } + > li { + margin: 0; + padding: 0; + } + ////// Pull exclude facets to the right, align with accordion/collapse triangles and headings .facet.excludable { padding-left: 1.5em; @@ -2759,8 +2778,8 @@ body:not(.offcanvas) .sidebar { //// Offcanvas is used to widths of 767px ($screen-xs-max) -@media (max-width: $screen-xs-max) { - .offcanvas.active .sidebar { +.offcanvas.active .sidebar { + @media (max-width: $screen-xs-max) { padding-right: ($grid-gutter-width / 2); } } @@ -2828,10 +2847,28 @@ body:not(.offcanvas) .sidebar { -ms-word-break: break-all; word-break: break-all; // fallback for hyphens: auto (Chrome on Desktop and bug on Mozilla for capitalized words) + a, .text { -ms-word-break: break-word; word-break: break-word; } + + a { + text-decoration: none; + } + } + + // special case: on sm-size AND search result facats + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + & div[id^='side-panel-'] { + .facet { + a, + .text { + -ms-word-break: initial; + word-break: initial; + } + } + } } } @@ -2878,6 +2915,8 @@ body:not(.offcanvas) .sidebar { &.overdue { .myresearch-menu & { color: $white; + // unset height attribute from .facet .badge {} definition for better looks + max-height: unset; padding: 3px 7px; } } @@ -3257,13 +3296,12 @@ input { // CHANNELS - -.slick-arrow:not(.slick-disabled).slick-next:focus:before, -.slick-arrow:not(.slick-disabled).slick-next:hover:before, -.slick-arrow:not(.slick-disabled).slick-prev:focus:before, -.slick-arrow:not(.slick-disabled).slick-prev:hover:before { - color: $slick-arrow-hover-color; - outline: 2px $black dotted; +.slick-arrow:not(.slick-disabled).slick-next, +.slick-arrow:not(.slick-disabled).slick-prev { + &:focus::before, + &:hover::before { + color: $slick-arrow-hover-color; + outline: 2px $black dotted; + } } - // CHANNELS - END diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml index 3412cfef99ca6959911d15635e8671a04dcd5879..3c1233c8b90ba4f5341f0a60d79e1a1bfa024bae 100644 --- a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml +++ b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml @@ -38,8 +38,8 @@ $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('list' <div class="result-body"> <div class="resultItemLine1"> <?php $missing = $this->driver instanceof \VuFind\RecordDriver\Missing; ?> - <?php $describedById = $driver->getSourceIdentifier() . '|' . $driver->getUniqueId(); ?> <?php if ($missing && $this->driver->isCachedRecord()): ?> + <?php $describedById = $driver->getSourceIdentifier() . '|' . $driver->getUniqueId(); ?> <span id="<?=$describedById?>" class="title" lang=""><?=$this->record($this->driver)->getTitleHtml()?></span> <p class="alert alert-info"> <?= $this->translate('record_from_cache')?> @@ -48,6 +48,7 @@ $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('list' <?php endif; ?> </p> <?php elseif (!$missing): ?> + <?php $describedById = $driver->getSourceIdentifier() . '|' . $driver->getUniqueId(); ?> <a href="<?=$this->recordLink()->getUrl($this->driver)?>" class="getFull" data-view="<?=$this->params->getOptions()->getListViewOption() ?>"> <span id="<?=$describedById?>" class="title" lang=""><?=$this->record($this->driver)->getTitleHtml()?></span> </a> diff --git a/themes/finc/templates/footer.phtml b/themes/finc/templates/footer.phtml index b39d33f6adef441e6b1a61844b7c4a6b7531d49c..e0e0eae30b028c7913c59d682515273782a6d5ad 100644 --- a/themes/finc/templates/footer.phtml +++ b/themes/finc/templates/footer.phtml @@ -38,7 +38,7 @@ <?= $this->transEsc("Footer-Powered-By-Text") ?> </span> <?= $this->externalLink("https://vufind.org", '<img src="' . $this->imageLink('vufind_logo.png') . '" alt="' . $this->translate('vufind-logo_alt') . '" aria-hidden="true"/>', ['title' => $this->translate('vufind-logo_title')]) ?> - <?= $this->externalLink("http://blog.finc.info", '<img src="' . $this->imageLink('finc_logo.png') . '" alt="' . $this->translate('finc-logo_alt') . '" aria-hidden="true"/>', ['title' => $this->translate('finc-logo_title'), 'aria-hidden' => 'true']) ?> + <?= $this->externalLink("https://finc.info", '<img src="' . $this->imageLink('finc_logo.png') . '" alt="' . $this->translate('finc-logo_alt') . '" aria-hidden="true"/>', ['title' => $this->translate('finc-logo_title'), 'aria-hidden' => 'true']) ?> </div> </div> </footer> diff --git a/themes/finc/templates/layout/layout.phtml b/themes/finc/templates/layout/layout.phtml index c2b6a370feb8ac53c20deecea4b26f0bd41c64b7..898b0e194a6543dee78370def3c9e928c51b3e5e 100644 --- a/themes/finc/templates/layout/layout.phtml +++ b/themes/finc/templates/layout/layout.phtml @@ -24,6 +24,13 @@ <?php if ($this->layout()->rtl) { $this->headLink()->appendStylesheet('vendor/bootstrap-rtl.min.css'); } ?> + <?php + if (strcmp($this->layout()->userLang, 'de') == 0) { + $this->headScript()->appendFile('vendor/bootstrap-accessibility-de.min.js'); + } else { + $this->headScript()->appendFile('vendor/bootstrap-accessibility-en.min.js'); + } + ?> <?=$this->headLink()?> <?=$this->headStyle()?> <?php diff --git a/themes/finc/templates/myresearch/menu.phtml b/themes/finc/templates/myresearch/menu.phtml index 32dd7c7cc725e4eb4a729bd3695345644e61b83c..a2348fc565404adbfdcaae793f20c57a52d8b1f2 100644 --- a/themes/finc/templates/myresearch/menu.phtml +++ b/themes/finc/templates/myresearch/menu.phtml @@ -8,114 +8,144 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <h2><?=$this->transEsc('Your Account')?></h2> <?php /* finc needs to add .facet-group class and classes on sub items for borders - CK */ ?> -<div class="myresearch-menu account-menu facet-group"> +<ul class="myresearch-menu account-menu facet-group"> <?php if ($this->userlist()->getMode() !== 'disabled'): ?> - <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active"' : ''?>> + <li class="facet"> + <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-star" aria-hidden="true"></i> <?=$this->transEsc('Favorites')?> </a> + </li> <?php endif; ?> <?php if ('ils-none' !== $this->ils()->getOfflineMode()): ?> <?php if ($this->ils()->checkCapability('getMyTransactions', $capabilityParams)): ?> - <a href="<?=$this->url('myresearch-checkedout')?>" class="flex checkedout<?=$this->active == 'checkedout' ? ' active' : ''?>"> - <span class="flex-col"><i class="fa fa-fw fa-book" aria-hidden="true"></i> <?=$this->transEsc('Checked Out Items')?></span> - <span class="checkedout-status status hidden"><i class="fa fa-spin fa-spinner" aria-hidden="true"></i></span> - <?php /* nxt line finc specific - CK */ ?> - <span id="getMyTransactions" class="itemCount pull-right no-padding"></span> - </a> + <li class="facet"> + <a href="<?=$this->url('myresearch-checkedout')?>" class="flex checkedout<?=$this->active == 'checkedout' ? ' active' : ''?>"> + <span class="flex-col"><i class="fa fa-fw fa-book" aria-hidden="true"></i> <?=$this->transEsc('Checked Out Items')?></span> + <span class="checkedout-status status hidden"><i class="fa fa-spin fa-spinner" aria-hidden="true"></i></span> + <?php /* nxt line finc specific - CK */ ?> + <span id="getMyTransactions" class="itemCount pull-right no-padding"></span> + </a> + </li> <?php endif; ?> <?php if ($this->ils()->checkFunction('getMyTransactionHistory', $capabilityParams)): ?> - <a href="<?=$this->url('myresearch-historicloans')?>"<?=$this->active == 'historicloans' ? ' class="active"' : ''?>> + <li class="facet"> + <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> + </li> <?php endif; ?> <?php if ($this->ils()->checkCapability('getMyHolds', $capabilityParams)): ?> - <a href="<?=$this->url('myresearch-holds')?>" class="flex<?=$this->active == 'holds' ? ' active' : ''?>"> + <li class="facet"> + <a href="<?=$this->url('myresearch-holds')?>" class="flex<?=$this->active == 'holds' ? ' active' : ''?>"> <span class="flex-col"><i class="fa fa-fw fa-flag" aria-hidden="true"></i> <?=$this->transEsc('Holds and Recalls')?></span> <span class="holds-status status hidden"><i class="fa fa-spin fa-spinner" aria-hidden="true"></i></span> <?php /* nxt line finc specific - CK */ ?> <span id="getMyHolds" class="itemCount pull-right no-padding"></span> </a> + </li> <?php endif; ?> <?php if ($this->ils()->checkFunction('StorageRetrievalRequests', $capabilityParams)): ?> - <a href="<?=$this->url('myresearch-storageretrievalrequests')?>" class="flex<?=$this->active == 'storageRetrievalRequests' ? ' active' : ''?>"> + <li class="facet"> + <a href="<?=$this->url('myresearch-storageretrievalrequests')?>" class="flex<?=$this->active == 'storageRetrievalRequests' ? ' active' : ''?>"> <span class="flex-col"><i class="fa fa-fw fa-archive" aria-hidden="true"></i> <?=$this->transEsc('Storage Retrieval Requests')?></span> <span class="storageretrievalrequests-status status hidden"><i class="fa fa-spin fa-spinner" aria-hidden="true"></i></span> <?php /* nxt line finc specific - CK */ ?> <span id="getMyStorageRetrievalRequests" class="itemCount pull-right no-padding"></span> </a> + </li> <?php endif; ?> <?php if ($this->ils()->checkFunction('ILLRequests', $capabilityParams)): ?> - <a href="<?=$this->url('myresearch-illrequests')?>" class="flex<?=$this->active == 'ILLRequests' ? ' active' : ''?>"> + <li class="facet"> + <a href="<?=$this->url('myresearch-illrequests')?>" class="flex<?=$this->active == 'ILLRequests' ? ' active' : ''?>"> <span class="flex-col"><i class="fa fa-fw fa-exchange" aria-hidden="true"></i> <?=$this->transEsc('Interlibrary Loan Requests')?></span> <span class="illrequests-status status hidden"><i class="fa fa-spin fa-spinner" aria-hidden="true"></i></span> <?php /* nxt line finc specific - CK */ ?> <span id="getMyILLRequests" class="itemCount pull-right no-padding"></span> </a> + </li> <?php endif; ?> <?php if ($this->ils()->checkCapability('getMyFines', $capabilityParams)): ?> - <a href="<?=$this->url('myresearch-fines')?>" class="flex<?=$this->active == 'fines' ? ' active' : ''?>"> + <li class="facet"> + <a href="<?=$this->url('myresearch-fines')?>" class="flex<?=$this->active == 'fines' ? ' active' : ''?>"> <span class="flex-col"><i class="fa fa-fw fa-usd" aria-hidden="true"></i> <?=$this->transEsc('Fines')?></span> <span class="fines-status status hidden"><i class="fa fa-spin fa-spinner" aria-hidden="true"></i></span> </a> + </li> <?php endif; ?> - <a href="<?=$this->url('myresearch-profile')?>"<?=$this->active == 'profile' ? ' class="active"' : ''?>> + <li class="facet"> + <a href="<?=$this->url('myresearch-profile')?>"<?=$this->active == 'profile' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-user" aria-hidden="true"></i> <?=$this->transEsc('Profile')?> </a> + </li> <?php if ($user && $user->libraryCardsEnabled()): ?> - <a href="<?=$this->url('librarycards-home')?>"<?=$this->active == 'librarycards' ? ' class="active"' : ''?>> + <li class="facet"> + <a href="<?=$this->url('librarycards-home')?>"<?=$this->active == 'librarycards' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-barcode" aria-hidden="true"></i> <?=$this->transEsc('Library Cards')?> </a> + </li> <?php endif; ?> <?php endif; ?> <?php if ($this->accountCapabilities()->getSavedSearchSetting() === 'enabled'): ?> - <a href="<?=$this->url('search-history')?>?require_login"<?=$this->active == 'history' ? ' class="active"' : ''?>> + <li class="facet"> + <a href="<?=$this->url('search-history')?>?require_login"<?=$this->active == 'history' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-search" aria-hidden="true"></i> <?=$this->transEsc('history_saved_searches')?> </a> + </li> <?php endif; ?> <?php if ($user): ?> - <a href="<?=$this->url('myresearch-logout')?>"> + <li class="facet"> + <a href="<?=$this->url('myresearch-logout')?>"> <i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> <?=$this->transEsc("Log Out")?> </a> + </li> <?php endif; ?> -</div> +</ul> <?php if ($this->auth()->isLoggedIn() && $this->auth()->getManager()->supportsPasswordChange()): ?> <h3><?=$this->transEsc('Preferences')?></h3> - <div class="myresearch-menu facet-group"> - <a href="<?=$this->url('myresearch-changepassword')?>"<?=$this->active == 'newpassword' ? ' class="active"' : ''?>> + <ul class="myresearch-menu facet-group"> + <li class="facet"> + <a href="<?=$this->url('myresearch-changepassword')?>"<?=$this->active == 'newpassword' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-lock" aria-hidden="true"></i> <?=$this->transEsc('Change Password')?> </a> - </div> + </li> + </ul> <?php endif; ?> <?php if ($user && $this->userlist()->getMode() !== 'disabled'): ?> <h3><?=$this->transEsc('Your Lists')?></h3> - <div class="myresearch-menu facet-group"> - <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active"' : ''?>> + <ul class="myresearch-menu facet-group"> + <li class="facet"> + <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-star" aria-hidden="true"></i> <?=$this->transEsc('Your Favorites')?> </a> + </li> <?php $lists = $user->getLists() ?> <?php foreach ($lists as $list): ?> <?php /* finc: keep icon inside + keep braces in badge!; CK*/ ?> - <a href="<?=$this->url('userList', ['id' => $list['id']])?>"<?=$this->active == 'list' . $list['id'] ? ' class="active"' : ''?>> + <li class="facet"> + <a href="<?=$this->url('userList', ['id' => $list['id']])?>"<?=$this->active == 'list' . $list['id'] ? ' class="active"' : ''?>> <i class="fa fa-fw fa-star-o" aria-hidden="true"></i> <?=$this->escapeHtml($list['title'])?> <span class="badge">(<?=$list->cnt?>)</span> </a> + </li> <?php endforeach; ?> - <a href="<?=$this->url('editList', ['id' => 'NEW'])?>"<?=$this->active == 'editlist/NEW' ? ' class="active"' : ''?>> + <li class="facet"> + <a href="<?=$this->url('editList', ['id' => 'NEW'])?>"<?=$this->active == 'editlist/NEW' ? ' class="active"' : ''?>> <i class="fa fa-fw fa-plus" aria-hidden="true"></i> <?=$this->transEsc('Create a List')?> </a> + </li> - </div> + </ul> <?php endif ?> <?php /* finc: This script is finc-specific - CK */ ?> <?php $script = <<<JS