diff --git a/composer.local.0.finc.json b/composer.local.0.finc.json index 4bc3bf8c419127ef97648a6a49be4b036b869eb8..76e927c42a02757ea1c8e17f33987c5f8038384b 100644 --- a/composer.local.0.finc.json +++ b/composer.local.0.finc.json @@ -17,6 +17,11 @@ "extra": { "vufind": { "modules": [ + "Zend\\Filter", + "Zend\\Hydrator", + "Zend\\Validator", + "Zend\\Serializer", + "Zend\\InputFilter", "finc" ] } diff --git a/devops/codecheck/rules/phpcs.xml b/devops/codecheck/rules/phpcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..2582e8367df172cbcd9b47644a796636d0f09e08 --- /dev/null +++ b/devops/codecheck/rules/phpcs.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<ruleset name="VuFind Coding Standards" namespace="VuFind\PHPCS"> + <description>Coding standards for VuFind.</description> + <file>../module</file> + <exclude-pattern>module/[^/]+/config/*</exclude-pattern> + <exclude-pattern>module/[^/]+/tests/*</exclude-pattern> + <exclude-pattern>/devops/*</exclude-pattern> + <arg name="extensions" value="php"/> + <rule ref="PEAR"> + <exclude name="PEAR.Commenting.FunctionComment.ParamCommentAlignment" /> + </rule> + <rule ref="PEAR.WhiteSpace.ObjectOperatorIndent"> + <properties> + <property name="multilevel" value="true" /> + </properties> + </rule> +</ruleset> diff --git a/devops/codecheck/rules/vufind.php-cs-fixer.php b/devops/codecheck/rules/vufind.php-cs-fixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d55a4188a9735aca2f6f8032e932c3b3dc55081d --- /dev/null +++ b/devops/codecheck/rules/vufind.php-cs-fixer.php @@ -0,0 +1,93 @@ +<?php + /** + * Configuration for VuFind 8 + */ + $finder = new PhpCsFixer\Finder(); + $finder->in(__DIR__ . '/../../../config') + ->in(__DIR__ . '/../../../module') + ->in(__DIR__ . '/../../../public'); + + $rules = [ + 'align_multiline_comment' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => [ + 'default' => 'single_space', + 'operators' => ['=' => null, '=>' => null], + ], + 'blank_line_after_namespace' => true, + 'braces' => true, + 'cast_spaces' => ['space' => 'none'], + 'class_attributes_separation' => ['elements' => ['method' => 'one', 'property' => 'one']], + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => ['case' => 'lower'], + 'elseif' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'full_opening_tag' => true, + 'function_declaration' => true, + 'function_typehint_space' => true, + 'indentation_type' => true, + 'is_null' => true, + 'line_ending' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'short'], + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'magic_constant_casing' => true, + 'method_argument_space' => true, + 'native_function_casing' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_before_namespace' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unused_imports' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'ordered_imports' => true, + 'phpdoc_no_access' => true, + 'php_unit_dedicate_assert_internal_type' => true, + 'php_unit_expectation' => true, + 'php_unit_method_casing' => true, + 'php_unit_mock' => true, + 'php_unit_no_expectation_annotation' => true, + 'pow_to_exponentiation' => true, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'short_scalar_cast' => true, + 'standardize_not_equals' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_null_coalescing' => true, + 'visibility_required' => true, + ]; + + $cacheDir = __DIR__ . '/../../../data/cache/.php_cs_cache'; + if (!is_dir($cacheDir)) { + mkdir($cacheDir); + } + + $config = new PhpCsFixer\Config(); + return $config->setCacheFile($cacheDir . '/.code.cache') + ->setRiskyAllowed(true) + ->setRules($rules) + ->setFinder($finder); diff --git a/devops/codecheck/rules/vufind_templates.php-cs-fixer.php b/devops/codecheck/rules/vufind_templates.php-cs-fixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ff3070f91609396792e322f563e652fe00ff510b --- /dev/null +++ b/devops/codecheck/rules/vufind_templates.php-cs-fixer.php @@ -0,0 +1,83 @@ +<?php +/** + * Configuration for VuFind 8 + */ +$finder = new PhpCsFixer\Finder(); +$finder->in(__DIR__ . '/../../../themes') + ->name('*.phtml'); + +$rules = [ + 'align_multiline_comment' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => [ + 'default' => 'single_space', + ], + 'blank_line_after_namespace' => true, + //'braces' => true, // disabled because we don't want to create inconsistent indentation, but useful to normalize control structure spacing + 'cast_spaces' => ['space' => 'none'], + 'class_attributes_separation' => ['elements' => ['method' => 'one', 'property' => 'one']], + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => ['case' => 'lower'], + 'elseif' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'full_opening_tag' => true, + 'function_declaration' => true, + 'function_typehint_space' => true, + 'indentation_type' => true, + 'is_null' => true, + 'line_ending' => true, + 'list_syntax' => ['syntax' => 'short'], + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'magic_constant_casing' => true, + 'method_argument_space' => true, + 'native_function_casing' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_before_namespace' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_trailing_whitespace' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unused_imports' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'ordered_imports' => true, + 'phpdoc_no_access' => true, + 'pow_to_exponentiation' => true, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'short_scalar_cast' => true, + 'standardize_not_equals' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + //'ternary_operator_spaces' => true, // disabled due to bug in php-cs-fixer 2.7.1 + 'ternary_to_null_coalescing' => true, + 'visibility_required' => true, +]; + +$cacheDir = __DIR__ . '/../../../data/cache/.php_cs_cache'; +if (!is_dir($cacheDir)) { + mkdir($cacheDir); +} + +$config = new PhpCsFixer\Config(); +return $config->setCacheFile($cacheDir . '/.template.cache') + ->setRiskyAllowed(true) + ->setRules($rules) + ->setFinder($finder); diff --git a/devops/i18n-merge.sh b/devops/i18n-merge.sh new file mode 100755 index 0000000000000000000000000000000000000000..2dafe51188842c1a773d98377d6ae3500002850c --- /dev/null +++ b/devops/i18n-merge.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# Copyright (C) 2021 Leipzig University Library +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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, see <http://www.gnu.org/licenses/>. +# +# @author Robert Lange <lange@ub.uni-leipzig.de> +# @license https://opensource.org/licenses/GPL-3.0 GNU GPLv3 + +# this script merges token files and removes unused / duplicate tokens with parent and target translation file *.ini +# assumes You are in projects base dir + +CACHE_FILE=$1; INSTANCE_FILE=$2; PARENT_FILE=$3; + +HAS_ERROR=0; +if [ -z "$CACHE_FILE" ] || [ -z "$INSTANCE_FILE" ]; then + SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + HAS_ERROR=1 + echo "Usage: $0 path/to/language/with/new/tokens path/to/language/file/of/instance [path/to/parent/file/of/instance]" + echo "example command 1: \"devops/i18n-merge.sh data/i18n/languages/de.ini de_zi4/languages/de.ini local/languages/de.ini\""; +else + if [ ! -f "$CACHE_FILE" ] + then + echo "Language File "$CACHE_FILE" does not exist on your filesystem."; HAS_ERROR=1; + fi + + if [ ! -f "$INSTANCE_FILE" ] + then + echo "Language File "$INSTANCE_FILE" does not exist on your filesystem."; HAS_ERROR=1; + fi +fi + +if [ -z "$CACHE_FILE" ]; then + if [ ! -f "$PARENT_FILE" ]; then + echo "Parent Language File "$PARENT_FILE" does not exist on your filesystem."; + HAS_ERROR=1; + fi +fi + +if [ $HAS_ERROR = 1 ] +then + #echo "exit 1: aborting" + exit 1; +fi + +echo "Read new tokens from $CACHE_FILE." +declare -A newTokens +declare -a newTokensOrder +while IFS= read -r line || [ -n "$line" ] # handle possible missing newline in last line +do + key=$(echo "$line" | awk -F: '{ st = index($0,"=");print substr($0,0,st-1)}') + value=$(echo "$line" | awk -F: '{ st = index($0,"=");print substr($0,st+1)}') + newTokens["$key"]="$value" + newTokensOrder+=("$key"); +done < $CACHE_FILE + +echo "Reading and minify existing tokens from $INSTANCE_FILE" +declare -A instanceTokens +declare -A instanceComments +declare -a instanceTokensOrder +i=0 +while IFS= read -r line || [ -n "$line" ]; # handle possible missing newline in last line +do + if [ -z "$line" ]; then + #echo "empty" + instanceTokensOrder+=("X_empty_X"); + elif [[ ${line:0:1} == "#" ]] || [[ ${line:0:1} == ";" ]]; then + #echo "comment" + instanceTokensOrder+=("X_comment_$i") + instanceComments["X_comment_$i"]="$line" + elif [[ ${line:0:11} == "@parent_ini" ]]; then + #echo "relative path to parent ini" + instanceTokensOrder+=("X_comment_$i") + instanceComments["X_comment_$i"]="$line" + #PARENT_FILE=$(echo "$line" | awk -F: '{ st = index($0,"=");print substr($0,st+3)} ' | sed 's/.$//') + #PARENT_FILE=$(echo "${PARENT_FILE#@(.)}") + #PARENT_FILE="${INSTANCE_FILE}/${PARENT_FILE}" + #echo "$PARENT_FILE" + else + key=$(echo "$line" | awk -F: '{ st = index($0,"=");print substr($0,0,st-1)}') + value=$(echo "$line" | awk -F: '{ st = index($0,"=");print substr($0,st+1)}') + #echo "$line" + # only add to instanceTokensOrder when key entry is NOT already existing -> remove duplicates / ignored by letting latest entry win + if [[ -n "${instanceTokens["$key"]}" ]]; then + echo "* remove duplicate token \"${key}\" with obsolete value ${instanceTokens["$key"]} by $value." + else + instanceTokensOrder+=("$key"); + fi + instanceTokens["$key"]="$value"; + fi + ((i=i+1)) +done < $INSTANCE_FILE + +if [ -f "$PARENT_FILE" ]; then + echo "Reading and minifying existing tokens from $PARENT_FILE..." + declare -A parentTokens + declare -a parentTokensOrder + while IFS= read -r line || [ -n "$line" ] + do + if [ -z "$line" ] || [[ ${line:0:1} == "#" ]] || [[ ${line:0:1} == ";" ]] || [[ ${line:0:11} == "@parent_ini" ]]; then + continue + else + key=$(echo "$line" | awk -F: '{ st = index($0,"=");print substr($0,0,st-1)}') + value=$(echo "$line" | awk -F: '{ st = index($0,"=");print substr($0,st+1)}') + #echo "$line" + # only add to instanceTokensOrder when key entry is NOT already existing -> remove duplicates / ignored by letting latest entry win + if [[ ${instanceTokens["$key"]} == "$value" ]] ; then + #instanceTokens["$key"]="$value"; + echo "* remove duplicate token \"${key}\" and $value with parent file." + instanceTokens["$key"]="X_duplicate_X"; + fi + fi + done < $PARENT_FILE +fi + +echo "Adding new token translations..." +for i in "${!newTokensOrder[@]}" +do + if [[ -n "${instanceTokens[${newTokensOrder[$i]}]}" ]]; then + echo "* replace token \"${newTokensOrder[$i]}\" with old value ${instanceTokens[${newTokensOrder[$i]}]} by new value $value" + else + instanceTokensOrder+=("${newTokensOrder[$i]}"); + echo "* add new token \"${newTokensOrder[$i]}\" with value ${newTokens[${newTokensOrder[$i]}]}" + fi + instanceTokens["${newTokensOrder[$i]}"]="${newTokens[${newTokensOrder[$i]}]}"; +done + +echo "4) Writing merged translations to instance file..." +true > "${INSTANCE_FILE}" +for i in "${!instanceTokensOrder[@]}" +do + key="${instanceTokensOrder[$i]}" + if [[ "$key" == "X_empty_X" ]]; then + #echo "empty" + echo -en '\n' >> "${INSTANCE_FILE}" + elif [[ ${key:0:10} == "X_comment_" ]]; then + #echo "comment" + echo "${instanceComments[${key}]}" >> "${INSTANCE_FILE}" + elif [[ "${instanceTokens[${key}]}" != "X_duplicate_X" ]]; then + #echo "$key" + echo "${key}=${instanceTokens[${key}]}" >> "${INSTANCE_FILE}" + fi +done diff --git a/install-codestylechecker.sh b/install-codestylechecker.sh new file mode 100755 index 0000000000000000000000000000000000000000..85003b50dd118d0a687fb5a38ff8377e9923d91f --- /dev/null +++ b/install-codestylechecker.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +red="\e[0;91m" +green="\e[0;92m" +clean="\e[0m" + +if [ -z $1 ] +then + echo -e "\n${red}Nicht genau angegeben was ich tun soll.${clean}\n" + echo -e "---------------------------------------------------------------------------" + echo -e "\n ./install-codestylechecker.sh install - install the checker and hooks\n" + echo -e "\n ./install-codestylechecker.sh update - update the checker and hooks\n" + echo -e "--------------------------------------------------------------------------\n" + + exit 1 +fi + +if [ $(which curl) != "/usr/bin/curl" ] +then + echo -e "\n${red}Benötigt curl.\n" + exit 1 +fi + +gitdir=$(pwd) + +reset + +if [ $1 = "install" ] +then + + gitdir=$(pwd) + + echo -e "${green}Install...${clean}\n" + + mkdir -p $HOME/bin/codestylechecker + + cd $HOME/bin/codestylechecker + + curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar + curl -OL https://alpha.finc.info/vufind2/logs/alpha/CodeSniffer.conf + curl -OL https://alpha.finc.info/vufind2/logs/alpha/diffFilter.phar + curl -OL https://alpha.finc.info/vufind2/logs/alpha/php-cs-fixer3 + + chmod +x $HOME/bin/codestylechecker/phpcs.phar + chmod +x $HOME/bin/codestylechecker/diffFilter.phar + chmod +x $HOME/bin/codestylechecker/php-cs-fixer3 + + echo -e "\n${green}Kopiere Hooks nach ${gitdir}/.git/hooks\n${clean}" + + cd $gitdir/.git/hooks/ + + mv pre-commit pre-commit.old.$(date +%Y-%m-%d) + mv pre-push pre-push.old.$(date +%Y-%m-%d) + + curl -OL https://alpha.finc.info/vufind2/logs/alpha/pre-commit + curl -OL https://alpha.finc.info/vufind2/logs/alpha/pre-push + + chmod +x $gitdir/.git/hooks/pre-commit + chmod +x $gitdir/.git/hooks/pre-push + + echo -e "\n${green}Ready${clean}\n" + echo -e "\n${green}Activate PHP CS Fixer in PHPSorm: https://projekte.ub.uni-leipzig.de/projects/finc-intern/wiki/Code-Quality-Checking_mit_PHPStorm${clean}\n" + exit 0 + + elif [ $1 = "update" ] + then + + echo -e "\n${green}Update...${clean}\n" + + cd $HOME/bin/codestylechecker + + curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar + + chmod +x $HOME/bin/codestylechecker/phpcs.phar + + cd $gitdir/.git/hooks/ + + mv pre-commit pre-commit.old.$(date +%Y-%m-%d) + mv pre-push pre-push.old.$(date +%Y-%m-%d) + + curl -OL https://alpha.finc.info/vufind2/logs/alpha/pre-commit + curl -OL https://alpha.finc.info/vufind2/logs/alpha/pre-push + + chmod +x $gitdir/.git/hooks/pre-commit + chmod +x $gitdir/.git/hooks/pre-push + + echo -e "\n${green}Ready.${clean}\n" + exit 0 + fi + +echo -e "\n${red}Nix ist passiert.${clean}\n" + +exit 1 + +#heiko.wolf@uni-leipzig.de, 2021 diff --git a/local/config/vufind/FincILS.ini b/local/config/vufind/FincILS.ini index f288a15ef6a89018c6e24217a42ae9db35309f06..d255b92b531bdf33863c64144edbdbd21e2ce8a3 100644 --- a/local/config/vufind/FincILS.ini +++ b/local/config/vufind/FincILS.ini @@ -95,4 +95,11 @@ queryIls[] = 'getFacetAvail:Local' ; through user input. E.g. to select an email profile according to the selected ; pickUpLocation you have to set the value to "pickUpLocation". ; By default it is left empty using the default profile "EmailHold". -;emailProfileSelector = +;emailProfileSelector = + +; the ILLRequests section contains regex patterns for inter library loan item detection +; itemPattern matches the item ID, labelPattern matches the item's label +; as used in a PAIA regex filter +;[ILLRequests] +;itemPattern = "/^(?:(?!DE-15)).*$/" +;labelPattern = "/^ILL Status String$" \ No newline at end of file diff --git a/local/config/vufind/FincLibero.ini b/local/config/vufind/FincLibero.ini new file mode 100644 index 0000000000000000000000000000000000000000..3b73011fd7f88f3a4f2e1cf71a1d30fe1aea2eac --- /dev/null +++ b/local/config/vufind/FincLibero.ini @@ -0,0 +1,57 @@ +; General Settings for all FincLibero instances +[Parent_Config] +;inherits FincILS settings from same directory +relative_path=FincILS.ini + +; entries in [RequiredConfig] denote settings that are tested during initialization +; of FincLibero. Syntax is +; <INI Section>[<key within section>] = true for required, false for not required +; e.g. General[departmentLocationBase] = true makes FincLibero::init() +; throw an InitException in case [General]->departmentLocationBase is not set +; in any of FincLibero.ini or FincILS.ini +;[RequiredConfig] +;General[departmentLocationBase] = true +;General[titleHoldLimitations] = true +;General[pickUpLocationPatterns] = true +;General[requestableLimitations] = true +;General[holdableLimitations] = true +;General[recallableLimitations] = true +;General[awlLimitations] = true +;General[stackURIs] = true +;General[readingRoomURIs] = true + +; the following section is here to list info and examples for the settings +; that may be defined as required in [RequiredConfig] +; examples collected in DE-15 and DE-Zwi2 +;[General] +; Pattern that will extend uris for department +;departmentLocationBase = "http://data.ub.uni-leipzig.de/resource/DE-Zwi2/department/zw" + +; Limitations that will trigger an action. +;titleHoldLimitations[] = "DE-Zwi2:OrderTitle:[0-9]+X?" + +; Patterns that will identify limitations as pickUpLocations +;pickUpLocationPatterns[] = "/^http:\/\/data\.ub\.uni-leipzig\.de\/resource\//" + +; Limitations that will trigger the placeStorageRetrievalRequest action +;requestableLimitations[] = DE-15:OrderRequired + +; Limitations that will trigger the placeHold action +;holdableLimitations[] = DE-15:URI + +; Limitations that will trigger a recall action (currently not used). +;recallableLimitations[] = DE-15:ReservationPossible + +; Limitations that will identify the item for being bound to another item. +;awlLimitations[] = DE-15:OrderViaBoundItem + +; URIs that will be used for stack views +;stackURIs[] = "http://data.ub.uni-leipzig.de/resource/DE-15/pickup/zw01thek" + +; URIs that will be used for reading room views +;readingRoomURIs[] = "http://data.ub.uni-leipzig.de/resource/DE-15/pickup/zw01thekfo" + +; use Wachtl service for departments instead of DAIA details +;useWachtlPickupLocations = false +; use locationid and not customData +;useDaiaLocationId = true diff --git a/local/config/vufind/Resolver.ini b/local/config/vufind/Resolver.ini index 421d09a220eaeb7735c9ea065cfd369b9d97ded6..f9f52132dd8e056175a5da7659ebae3742200089 100644 --- a/local/config/vufind/Resolver.ini +++ b/local/config/vufind/Resolver.ini @@ -1,2 +1,91 @@ +;#################################################################### +;##################### DO NOT DELETE THIS HEADER #################### +;################### Leipzig University Library © 2015 ############## +; +; This is the ISIL-instance-specific default INI-file and inherits +; all the settings from the INI-file defined in [Parent_Config] which +; points to the default INI-file located in the folder vufind2/local +; + +;[Parent_Config] +;relative_path = ../../../local/config/vufind/Resolver.ini + +; A comma-separated list of config sections from the parent which should be +; completely overwritten by the equivalent sections in this configuration; +; any sections not listed here will be merged on a section-by-section basis. +;override_full_sections = "Ezb" + +; +; Add instance-specific customization after this header. +; +;##################### DO NOT DELETE THIS HEADER #################### +;#################################################################### + [General] -embed_auto_load = true \ No newline at end of file +; configure active resolvers (need to match sections below) +;active_resolvers = Redi,Ezb +; VuFind resolver integration settings (apply to all active resolver) +;window_settings = "toolbar=no,location=no,directories=no,buttons=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=550,height=600" +;show_in_results = false ; include in search results +;show_in_record = false ; include in core record metadata +;show_in_holdings = true ; include in holdings tab of record view +;embed = true +embed_auto_load = true +;replace_other_urls = true + +;[Redi] +;url = "https://www.redi-bw.de/links/{libraryhandle}" +;rfr_id = {library catalog url} +;resolver = redi + +; If you want to display a graphical link to your link resolver, uncomment the +; settings below. graphic should be a URL; graphic_width and graphic_height +; should be sizes in pixels. +;graphic = "http://myuniversity.edu/images/findIt.gif" +;graphic_width = 50 +;graphic_height = 20 + +; If your link resolver can render an image in response to an OpenURL, you can +; specify the base URL for image generation here: +;dynamic_graphic = "http://my-link-resolver/image" + +; If dynamic_graphic is set above, the dynamic image can be used instead of the +; standard text or static-image-based OpenURL link (true), it can be disabled +; (false), or it can be displayed in addition to the regular link ("both"). +;image_based_linking_mode = both + +[Ezb] +;url = "https://services.dnb.de/fize-service/gvr/full.xml" +;rfr_id = {library catalog url} +;resolver = ezb + +; If you want to display a graphical link to your link resolver, uncomment the +; settings below. graphic should be a URL; graphic_width and graphic_height +; should be sizes in pixels. +;graphic = "http://myuniversity.edu/images/findIt.gif" +;graphic_width = 50 +;graphic_height = 20 + +; If your link resolver can render an image in response to an OpenURL, you can +; specify the base URL for image generation here: +;dynamic_graphic = "http://my-link-resolver/image" + +; If dynamic_graphic is set above, the dynamic image can be used instead of the +; standard text or static-image-based OpenURL link (true), it can be disabled +; (false), or it can be displayed in addition to the regular link ("both"). +;image_based_linking_mode = both + +; EZB resolver driver specific settings + +;custom_params[] = "zdbid:getZdbId" +;custom_params[] = "doi:getDOI" +;sid = "" +;bibid = "" +;sigel = "" +;isil = "" +;bik = "" +;validStatesList = "0,1,2,3" +; Limit resolver results to EZB entries only (default false). +;ezb_only = true +; Limit resolver results to ZDB entries only (default false). +;zdb_only = true \ No newline at end of file diff --git a/local/config/vufind/config.ini b/local/config/vufind/config.ini index 1fac69a4f0fc852f51fc26ce03bcf4e657e0330a..a7c4da375e38acf6bfa3d1957aaddb39f36e5164 100644 --- a/local/config/vufind/config.ini +++ b/local/config/vufind/config.ini @@ -26,6 +26,8 @@ autoConfigure = true url = http://localhost/vufind_generic email = dummy@vufind.org title = "Generic Katalog (devel)" +; The separator used between page name and site name in the <title> tag of pages. +titleSeparator = "::" ; This is the default theme for non-mobile devices (or all devices if mobile_theme ; is disabled below). Available standard themes: ; bootstrap3 = HTML5 theme using Bootstrap 3 + jQuery libraries, with minimal @@ -1565,17 +1567,6 @@ nbn[pattern] = "^urn:nbn:" nbn[search] = "urn:nbn:" nbn[replace] = "http://nbn-resolving.de/urn:nbn:" -; This section defines urls to match with record entries of external ILS. -; - The url pattern is defined as value with %s replacement placeholder. -; - The variable name indicates which identifier should be taken as replacement. -; Currently possibles values are _id_ for unique solr id (finc.id) and _ppn_ as -; record id of data other provider. -; - The variable key indicates the institution for those replacement should be take -; place. -[ExternalAccess] -; ppn[DE-D13] = "http://webopac.skd.museum/libero/WebopacOpenURL.cls?ACTION=DISPLAY&RID=%s" -; id[DE-14] = "http://katalogbeta.slub-dresden.de/id/%s/" - ; ***************** ; * EOF finc ; ***************** diff --git a/local/config/vufind/iconMapping.ini b/local/config/vufind/iconMapping.ini new file mode 100644 index 0000000000000000000000000000000000000000..892e76ad27f7e134956d10e88b428d431b7da486 --- /dev/null +++ b/local/config/vufind/iconMapping.ini @@ -0,0 +1,119 @@ +;#################################################################### +;##################### DO NOT DELETE THIS HEADER #################### +;################### Leipzig University Library © 2021 ############## +; +; This is the SIGEL-instance-specific default INI-file and inherits +; all the settings from the INI-file defined in [Parent_Config] which +; points to the default INI-file located in the folder vufind2/local +; + +[StyleBasedIcons] +; use facet_avail facet as key and FacetAvail-Array as Mapping +type = "FacetAvail" +; combine format and facet_avail, for example AudioLocal +; type = "Formats_FacetAvail" +; use true to combine entries or false to use first entries as key +concatenateKeys = false +; default icons for field format: Formats | getFacetAvail +; Formats[default] = "" +; Formats[unknown] = "" + +; finc default mapping for facet_avail +FacetAvail[marcfincpda] = "fa-home passive" +FacetAvail[object] = "fa-home object" +FacetAvail[local] = "fa-home" +FacetAvail[online] = "fa-globe" +FacetAvail[free] = "fa-globe" +FacetAvail[missing] = "fa-question" +FacetAvail[default] = "fa-book" +FacetAvail[unknown] = "fa-book" + +; finc default mapping for format +Formats[article] = "fa-file-text-o" +Formats[preprint] = "fa-file-text-o" +Formats[book] = "fa-book" +Formats[notatedmusic] = "fa-music" +Formats[ebook] = "fa-globe" +Formats[electronicarticle] = "fa-globe" +Formats[map] = "fa-map" +Formats[conferenceproceedings] = "fa-file-text-o" +Formats[bookcomponentpart] = "fa-book" +Formats[thesis] = "fa-file-text-o" +Formats[serialcomponentpart] = "fa-file-text-o" +Formats[journal] = "fa-file-text" +Formats[electronicserialcomponentpart] = "fa-globe" +Formats[electronicjournal] = "fa-globe" +Formats[bibliography] = "fa-book" +Formats[exhibitioncatalogue] = "fa-file-text" +Formats[stillimage] = "fa-picture-o" +Formats[microfiche] = "fa-floppy-o" +Formats[electronicbook] = "fa-globe" +Formats[serial] = "fa-newspaper-o" +Formats[electronicthesis] = "fa-globe" +Formats[electronicmanuscript] = "fa-globe" +Formats[illustratedbook] = "fa-book" +Formats[monographseries] = "fa-book" +Formats[electronicbookcomponentpart] = "fa-globe" +Formats[biography] = "fa-book" +Formats[cdrom] = "fa-floppy-o" +Formats[commemorativepublication] = "fa-file-text" +Formats[manuscript] = "fa-file-text" +Formats[electronicmusicrecording] = "fa-music" +Formats[mixedmaterials] = "fa-cube" +Formats[kit] = "fa-cube" +Formats[specialprint] = "fa-file-text" +Formats[electronicserial] = "fa-globe" +Formats[autobiography] = "fa-book" +Formats[twodimensionalmovingimage] = "fa-file-video-o" +Formats[microfilmreel] = "fa-film" +Formats[video] = "fa-file-video-o" +Formats[photo] = "fa-picture-o" +Formats[othercomputercarrier] = "fa-floppy-o" +Formats[othermicroformcarrier] = "fa-floppy-o" +Formats[volume] = "fa-book" +Formats[newspaper] = "fa-newspaper-o" +Formats[dvdrom] = "fa-floppy-o" +Formats[dvdvideo] = "fa-file-video-o" +Formats[onlineresource] = "fa-globe" +Formats[database] = "fa-database" +Formats[performedmusic] = "fa-music" +Formats[musicalscore] = "fa-music" +Formats[computerdataset] = "fa-file-code-o" +Formats[textbook] = "fa-book" +Formats[cartographicimage] = "fa-picture-o" +Formats[electronicnewspaper] = "fa-globe" +Formats[computerdisccartridge] = "fa-floppy-o" +Formats[videocassette] = "fa-file-video-o" +Formats[microfilmroll] = "fa-film" +Formats[spokenword] = "fa-file-audio-o" +Formats[videodisc] = "fa-file-video-o" +Formats[comicbook] = "fa-book" +Formats[sheet] = "fa-file-text-o" +Formats[floppydisc] = "fa-floppy-o" +Formats[cd] = "fa-floppy-o" +Formats[website] = "fa-globe" +Formats[object] = "fa-cube" +Formats[serialpart] = "fa-file-text" +Formats[threedimensionalform] = "fa-cube" +Formats[otheraudiocarrier] = "fa-file-audio-o" +Formats[computerchipcartridge] = "fa-floppy-o" +Formats[electronicschoolpublication] = "fa-globe" +Formats[dvdaudio] = "fa-file-audio-o" +Formats[audiobook] = "fa-file-audio-o" +Formats[audiodisc] = "fa-file-audio-o" +Formats[audiocassette] = "fa-file-audio-o" +Formats[weblog] = "fa-globe" +Formats[tactileimage] = "fa-picture-o" +Formats[threedimensionalmovingimage] = "fa-picture-o" +Formats[software] = "fa-file-code-o" +Formats[usbflashdrive] = "fa-floppy-o" +Formats[vhs] = "fa-file-video-o" +Formats[dvd] = "fa-file-video-o" +Formats[othervideocarrier] = "fa-file-video-o" +Formats[audiocartridge] = "fa-file-audio-o" +Formats[default] = "fa-question" +Formats[electronicintegratingresource] = "fa-globe" +Formats[electronicbookpart] = "fa-globe" +Formats[electronicbookcomponentpart] = "fa-globe" +Formats[electronicproceeding] = "fa-globe" +Formats[norm] = "fa-balance-scale" diff --git a/local/languages/DDS/de.ini b/local/languages/DDS/de.ini index 9bcc3f8b5be5a42205e89ff27801a685aa160250..fc735434269263d722f16de3da3a8790f14fda6b 100644 --- a/local/languages/DDS/de.ini +++ b/local/languages/DDS/de.ini @@ -21,4 +21,10 @@ dds_text_mandatory_fields = "Pflichtfelder" ; Flashmessages dds_order_success = "Ihre Bestellung wurde erfolgreich an die Universitätsbibliothek Leipzig übermittelt." -dds_restriction_text = "Dieser Dienst steht nur Mitarbeiterinnen und Mitarbeitern der Universität sowie der Medizinischen Fakultät (Universitätsklinikum) zur Verfügung." \ No newline at end of file +dds_restriction_text = "Dieser Dienst steht nur Mitarbeiterinnen und Mitarbeitern der Universität sowie der Medizinischen Fakultät (Universitätsklinikum) zur Verfügung." +dds_issn = "Bitte eine korrekte ISSN-10 oder ISSN-13 angeben." +dds_error_publishdate_too_short = "Mindestens ein zweistelliges Erscheinungsjahr angeben." +dds_error_publishdate_too_long = "Maximal ein vierstelliges Erscheinungsjahr angeben." +acquisition_error_publishdate_pattern = "Bitte ein korrektes Jahr eingeben." +dds_error_pages_too_short = "Mindestens einstellige Seitenzahlen angeben." +acquisition_error_pages_pattern = "Bitte einen Seitenzahl (z.B. 20 oder 20-29) angeben." \ No newline at end of file diff --git a/local/languages/DDS/en.ini b/local/languages/DDS/en.ini index d4d3d579790d193b58ee6134fbfe1d9b6e6a3602..f5f96b23225173a5ee4773c3d2eec5d6c5122cfe 100644 --- a/local/languages/DDS/en.ini +++ b/local/languages/DDS/en.ini @@ -21,4 +21,10 @@ dds_text_mandatory_fields = "Mandatory fields" ; Flashmessages dds_order_success = "Your order has been sent successfully." -dds_restriction_text = "This service is only available for members of Leipzig University and department of medicine (Universitätsklinikum)." \ No newline at end of file +dds_restriction_text = "This service is only available for members of Leipzig University and department of medicine (Universitätsklinikum)." +dds_issn = "Only correct ISSN-10 or ISSN-13" +dds_error_publishdate_too_short = "min. a two digit publishdate" +dds_error_publishdate_too_long = "max. a four digit publishdate" +acquisition_error_publishdate_pattern = "Please enter a correct year." +dds_error_pages_too_short = "min. page 1" +acquisition_error_pages_pattern = "Please specify a number of pages (e.g. 20 or 20-29). " \ No newline at end of file diff --git a/local/languages/LiberoAccount/de.ini b/local/languages/LiberoAccount/de.ini new file mode 100644 index 0000000000000000000000000000000000000000..2607d8ef6eec11a949cfb834357dfb934c5174d5 --- /dev/null +++ b/local/languages/LiberoAccount/de.ini @@ -0,0 +1,8 @@ +# Change pin account action +change_user_pin = "PIN für Selbstverbuchung ändern" +new_pin = "Neue PIN" +confirm_new_pin = "Neue PIN bestätigen" +new_pin_missmatch = "PIN Eingaben stimmen nicht überein" +set_pin_success = "Die PIN wurde erfolgreich geändert" +set_pin_error = "Änderung der PIN gescheitert. Wenden Sie sich bitte an das Bibliothekspersonal." +set_pin_note = "Hinweis: Die PIN muss vierstellig sein und darf nur aus Ziffern bestehen." diff --git a/local/languages/LiberoAccount/en.ini b/local/languages/LiberoAccount/en.ini new file mode 100644 index 0000000000000000000000000000000000000000..7c3213fad165f2815bb9aa2d8c1f84b09cf9ed37 --- /dev/null +++ b/local/languages/LiberoAccount/en.ini @@ -0,0 +1,8 @@ +# Change pin account action +change_user_pin = "Change PIN" +new_pin = "New PIN" +confirm_new_pin = "Confirm new PIN" +new_pin_missmatch = "Confirmation of PIN is not valid. Please try it again" +set_pin_success = "New PIN was set successfully" +set_pin_error = "Errors occurred while changing PIN. Please ask on the service desk." +set_pin_note = "Please note that the PIN must consist of four digits only (no letters, no symbols ...)." diff --git a/local/languages/de.ini b/local/languages/de.ini index 654d7be086de4d27bf9376af9f0a928533a9cb92..43e56bfa00b0e74417949d16562cc638d6d70e5d 100644 --- a/local/languages/de.ini +++ b/local/languages/de.ini @@ -87,7 +87,7 @@ TactileText = "taktiler Text" Collage = Collage Drawing = Zeichnung Painting = Gemälde -Print = Druck +Print = "Drucken" Photonegative = Photonegativ FlashCard = "Flash Card" Chart = "Tabelle / Grafik" @@ -270,7 +270,7 @@ Electronic Resource (Remote Access) = "Elektronische Ressource im Fernzugriff" Electronic Resources = Online-Ressourcen Electronic Serial = "Elektronische Zeitschrift" Electronic Thesis = "Elektronische Hochschulschrift" -Email = "E-Mail" +Email = "Mailen" Email Address = "E-Mail-Adresse" Email Record = "E-Mail-Eintrag" Email address is invalid = "Die E-Mail-Adresse ist ungültig" @@ -646,7 +646,7 @@ banner_link = "kommentieren Sie im Blog!" bookbag_confirm_empty = "Wollen Sie ihre Zwischenablage wirklich leeren?" bookbag_delete_selected = "Löschen" bookbag_delete = "Markiertes aus Zwischenablage löschen" -bookbag_email_selected = "E-Mail" +bookbag_email_selected = "Links zu ausgewählten Medien per E-Mail versenden" bookbag_email = "Markiertes per E-Mail versenden" bookbag_export_selected = "Export" bookbag_export = "Markiertes exportieren" @@ -715,12 +715,12 @@ errorcode_opac_error = "Es ist ein Fehler im Lokalsystem aufgetreten" errorcode_login_error = "Die Anmeldung des Benutzers am Lokalsystem ist gescheitert" errorcode_empty_member_code = "Es wurde keine Benutzernummer übergeben" errorcode_empty_password = "Es wurde kein Passwort übergeben" -errorcode_member_not_found = "Die Benutzernummer exisitiert nicht" +errorcode_member_not_found = "Die Benutzernummer existiert nicht" errorcode_password_validation_error = "Das übergebene Password ist nicht korrekt" errorcode_old_password_validation_error = "Die Kombination aus Benutzernummer und Kennwort ist nicht gültig. Bitte überprüfen Sie Ihre Eingabe." errorcode_empty_req_param_error = "Um das Formular erfolgreich zu senden, müssen alle erforderlichen Felder ausgefüllt sein" exclude_newspapers = "Ohne Zeitungsartikel" -exclude_filter = "Filter "%%filter_name%%" ausschließen" +exclude_filter = "Filter '%%filter_name%%' ausschließen" export_download = "Datei herunterladen" export_exporting = "Exportdatei erstellen" export_fail = "Ihre Datensätze wurden nicht exportiert" @@ -732,6 +732,8 @@ export_selected_favorites = "Ausgewählte Favoriten exportieren" export_success = "Export abgeschlossen" export_unsupported_format = "Nichtunterstütztes Exportformat" external = "außer Haus" +facet_select_hint = "Zum Einschränken der Ergebnisse können Sie unter folgenden Filtern wählen:" +facet_deselect_hint = "Zum Aufheben der Filterfunktion wählen Sie einen oder alle der folgenden Filtern ab:" fav_delete = "Ausgewählte Favoriten löschen" fav_delete_deleting = "Ihre ausgewählten Favoriten werden gelöscht." fav_delete_fail = "Leider ist ein Fehler aufgetreten. Ihre ausgewählten Favoriten wurden nicht gelöscht." @@ -881,8 +883,9 @@ online_resources = "Sonstige" or = "oder" create a new list = "erstellen Sie eine neue Liste" page_reload_hint = "Seite wird neu geladen" -page_reload_on_select_hint = "Seite wird bei Auswahl des Filters "%%filter_name%%" neu geladen" -page_reload_on_deselect_hint = "Seite wird neu geladen, wenn der Filter "%%filter_name%%" abgewählt wird" +page_reload_on_select_hint = "Seite wird bei Auswahl des Filters '%%filter_name%%' neu geladen" +page_reload_on_deselect_hint = "Seite wird neu geladen, wenn der Filter '%%filter_name%%' abgewählt wird" +page_reload_on_xclude_hint = "Seite wird neu geladen, wenn der Filter '%%filter_name%%' aus den Suchergebnissen ausgeschlossen wird" password_too_short = "Zu kurz" password_very_weak = "Sehr schwach" password_weak = "Schwach" @@ -1833,8 +1836,9 @@ Address-Contact-Hours = "Adresse, Kontakt" resolver_link_access_denied = "nicht verfügbar" resolver_link_access_limited = "Im Campusnetz verfügbar" resolver_link_access_open = "verfügbar" +resolver_link_access_unknown = "Titel ist beim Resolver-Service nicht bekannt" ; message to be shown upon empty resolver response -no_resolver_links = "Keine Online Links verfügbar." +no_resolver_links = "Keine Online-Links verfügbar." ; reset password reset_password_text = "Bitten füllen Sie dieses Formular aus, um Ihr Passwort zurücksetzen zu lassen. Sie erhalten an u.g. E-Mail-Adresse eine Benachrichtigung, nachdem wir das Passwort zurückgesetzt haben." @@ -1969,7 +1973,21 @@ support_by_dfg = "Die Nationallizenzen wurden gefördert durch die" (fa-home object) = "Online-Ressource" (fa-globe) = "Online-Ressource" (fa-home passive) = "beschaffbar" -(fa-question) = "entfernt / ersetzt" +(fa-question) = "unbekannt" +(fa-file-text-o) = "Artikel" +(fa-file-text) = "Zeitschrift" +(fa-book) = "Buch" +(fa-music) = "Musik" +(fa-file-video-o) = "Video" +(fa-floppy-o) = "Datenträger" +(fa-newspaper-o) = "Schriftenreihe" +(fa-picture-o) = "Bild" +(fa-cube) = "Vermischtes" +(fa-film) = "Film" +(fa-database) = "Datenbank" +(fa-file-code-o) = "Software" +(fa-file-audio-o) = "Audio" +(fa-balance-scale) = "Norm" ; HierarchyTree select hierarchyTreeSelect = "Ãœbergeordnete Werke:" @@ -1989,10 +2007,10 @@ ReliefPrint = Druckgraphik ; Accessibility Modal_description = "Sie befinden sich in einem Dialogfenster, das über dem Hauptinhalt der Seiten liegt. Drücken Sie die Escape-Taste oder die Schaltfläche 'Dialogfenster schließen', um das Fenster zu schließen und auf der Hauptseite weiterzuarbeiten." -Skip_navigation_links = "Sprunglinks zum Inhalt" +Skip_navigation_links = "Sprunglinks zur Suche und zum Inhalt" Skip to search = "Zum Suchbereich" Skip to content = "Zum Inhalt" -Skip to facet = "Zu ihrem ausgewählten Suchfilter "%%filter_name%%" springen" +Skip to facet = "Zu ihrem ausgewählten Suchfilter '%%filter_name%%' springen" skip-to = "Zu" ; Overwrite, only in de.ini necessary: @@ -2050,21 +2068,11 @@ DE-Kn38 = "Hochschule für Musik und Tanz Köln" Range-from-to = "Bereich von/bis" ; #17833 -form-legend = "Bitte füllen Sie alle Felder aus" form-button-submit = "Ausgefülltes Formular abschicken" ; #17601 offcanvas-toggler-search-tips = "Suchtipps einblenden" -; #17993 -; only for German translation -Email = "Mailen" -Print = "Drucken" -bookbag_email_selected = "Links zu ausgewählten Medien per E-Mail versenden" - -; #18441 -Skip_navigation_links = "Sprunglinks zur Suche und zum Inhalt" - ; #18019 & #18754 select_item = "Titel wählen" select_item_search_result = "Titel zum Mailen, Exportieren, Drucken, Speichern oder Merken auswählen" @@ -2079,3 +2087,14 @@ toggle-dropdown = "Untermenü aufklappen" missing_record_redirect = "Der aufgerufene Titel ist nicht vorhanden. Stattdessen wurde eine Suche ausgelöst" missing_record_exception = "Der aufgerufene Titel (%%id%%) ist nicht vorhanden." + +; #18611 unknown resolver state 10 +Unknown Electronic = "Titel ist beim Resolver-Service nicht bekannt" + +; #20826 +title_wrapper = "%%pageTitle%% %%titleSeparator%% %%siteTitle%%" + +load_tab_content_hint = "Klicken Sie hier, um den Inhalt der Registerkarte zu laden." + +; #20266 +Additional Titles = "Weitere Titel" diff --git a/local/languages/en.ini b/local/languages/en.ini index b8214638d2844e780925629d63b8b94b7c3793a0..d52e85f5cd4ccd89bc9c5507b1c997349dd45f9b 100644 --- a/local/languages/en.ini +++ b/local/languages/en.ini @@ -151,7 +151,6 @@ Article, E-Article = Article, E-Article Audio = Audio Book, E-Book = Book, E-Book Card = Card -DBIS = DBIS Journal, E-Journal = Journal, E-Journal Map = Map Notated Music = Notated Music @@ -705,7 +704,7 @@ errorcode_empty_password = "Some data was missing. No password was submitted" errorcode_member_not_found = "The member number does not exist" errorcode_password_validation_error = "Your passed password is not correct" errorcode_empty_req_param_error = "All required fields have to be filled to submit successfully the form" -exclude_filter = "Exclude filter "%%filter_name%%"" +exclude_filter = "Exclude filter '%%filter_name%%'" exclude_newspapers = "Exclude Newspaper Articles" export_download = "Download File" export_exporting = "Creating Export File" @@ -717,6 +716,8 @@ export_selected = "Export Selected" export_selected_favorites = "Export Selected Favorites" export_success = "Export Complete" export_unsupported_format = "Unsupported Export Format" +facet_select_hint = "Please select from the following filters to narrow down search results:" +facet_deselect_hint = "Please de-select one or all of the following filters to widen search results:" fav_delete = "Delete Selected Favorites" fav_delete_deleting = "Your favorite(s) are being deleted." fav_delete_fail = "Sorry, an error has occurred. Your favorite(s) were not deleted." @@ -844,8 +845,9 @@ on_reserve = "Reserves - Please contact staff" online_resources = "Online Resources" create a new list = "Create a New List" page_reload_hint = "Page will refresh immediately" -page_reload_on_select_hint = "Page will refresh when filter "%%filter_name%%" is selected" -page_reload_on_deselect_hint = "Page will refresh when filter "%%filter_name%%" is deselected" +page_reload_on_select_hint = "Page will refresh when filter '%%filter_name%%' is selected" +page_reload_on_deselect_hint = "Page will refresh when filter '%%filter_name%%' is deselected" +page_reload_on_xclude_hint = "Page will refresh when filter '%%filter_name%%' is selected for exclusion from the search results" password_too_short = "Too short" password_very_weak = "Very weak" password_weak = "Weak" @@ -973,7 +975,7 @@ DE-15-292 = "Leipzig University Library, Central Library Medicine" DE-105 = "Technische Universität Bergakademie Freiberg" DE-197 = "City Library Leipzig" DE-520 = "Dresden University of Applied Sciences" -DE-540 = "Dresden Academy of Fine Arts" +DE-540 = "Dresden University of Fine Arts" DE-1156 = "Folkwang University of Arts" DE-1972 = "Robert Schumann Academy Düsseldorf" DE-Ch1 = "Technische Universität Chemnitz" @@ -1943,6 +1945,7 @@ Address-Contact-Hours = "Address, Contact" resolver_link_access_denied = "not available" resolver_link_access_limited = "Available in Campus LAN" resolver_link_access_open = "available" +resolver_link_access_unknown = "Record unknown to resolver" ; message to be shown upon empty resolver response no_resolver_links = "No online links available." @@ -2071,7 +2074,21 @@ support_by_dfg = "National licenses were sponsored by " (fa-home object) = "online resource" (fa-globe) = "online resource" (fa-home passive) = "available on request" -(fa-question) = "removed or replaced" +(fa-question) = "unknown" +(fa-file-text-o) = "article" +(fa-file-text) = "journal" +(fa-book) = "book" +(fa-music) = "music" +(fa-file-video-o) = "video" +(fa-floppy-o) = "data carrier" +(fa-newspaper-o) = "Serial" +(fa-picture-o) = "picture" +(fa-cube) = "mixed" +(fa-film) = "movie" +(fa-database) = "database" +(fa-file-code-o) = "software" +(fa-file-audio-o) = "audio" +(fa-balance-scale) = "norm" ; HierarchyTree select hierarchyTreeSelect = "Parent items:" @@ -2092,7 +2109,7 @@ Modal_description = "This is a dialog window which overlays the main content of Skip_navigation_links = "Skip navigation links" Skip to search = "Skip to search" skip-to = "Skip to " -Skip to facet = "Skip to your selected search filter "%%filter_name%%"" +Skip to facet = "Skip to your selected search filter '%%filter_name%%'" License = "License" @@ -2132,14 +2149,12 @@ DE-Kn38 = "Hochschule für Musik und Tanz Köln" Range-from-to = "Range from/to" ; #17833 -form-legend = "Please fill in all fields to create an account" form-button-submit = "Submit the completed form" ; #17601 offcanvas-toggler-search-tips = "Show search help" ; #18441 -Skip_navigation_links = "Skip navigation links" ; #18019 & #18754 select_item = "Select item" @@ -2159,3 +2174,14 @@ toggle-dropdown = "Toggle Dropdown" missing_record_redirect = "Record unavailable. You have been redirected to a search." missing_record_exception = "Record %%id%% is unavailable." + +; #18611 unknown resolver state 10 +Unknown Electronic = "Record is unknown to the resolver service" + +; #20826 +title_wrapper = "%%pageTitle%% %%titleSeparator%% %%siteTitle%%" + +load_tab_content_hint = "Click to load tab content." + +; #20266 +Additional Titles = "Additional Titles" \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php b/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php index 7a102637b149db17253127d5bac9fdbf261ac169..e2ea81b2789d8026d12671753d15391057855f3e 100644 --- a/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php +++ b/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php @@ -6,7 +6,7 @@ * http://services.dnb.de/fize-service/gvr/full.xml * * API documentation is available at - * http://www.zeitschriftendatenbank.de/services/schnittstellen/journals-online-print + * http://www.zeitschriftendatenbank.de/services/journals-online-print * * PHP version 7 * @@ -136,7 +136,7 @@ class Ezb extends AbstractBase public function getResolverUrl($openURL) { // Unfortunately the EZB-API only allows OpenURL V0.1 and - // breaks when sending a non expected parameter (like an ISBN). + // breaks when sending a non expected parameter. // So we do have to 'downgrade' the OpenURL-String from V1.0 to V0.1 // and exclude all parameters that are not compliant with the EZB. @@ -216,9 +216,9 @@ class Ezb extends AbstractBase } if ($newKey !== false) { $downgraded[] = "$newKey=$value"; - } } } + } return implode('&', $downgraded); } diff --git a/module/finc/config/dds-form.php b/module/finc/config/dds-form.php new file mode 100644 index 0000000000000000000000000000000000000000..3d081ae7e0a56aed9ab83d2dfb1ebbc9c5867e98 --- /dev/null +++ b/module/finc/config/dds-form.php @@ -0,0 +1,403 @@ +<?php +/** + * Copyright (C) 2019 Leipzig University Library + * + * 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. + * + * @author Sebastian Kehr <kehr@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU GPLv2 + */ + +use fid\Hydrator\UserHydrator; +use Zend\Filter\Boolean; +use Zend\Filter\PregReplace; +use Zend\Filter\StringTrim; +use Zend\Form\Element\Checkbox; +use Zend\Form\Element\Email; +use Zend\Form\Element\Select; +use Zend\Form\Element\Submit; +use Zend\Form\Element\Text; +use Zend\Form\Element\Textarea; +use Zend\Validator\EmailAddress; +use Zend\Validator\Identical; +use Zend\Validator\Isbn; +use Zend\Validator\NotEmpty; +use Zend\Validator\Regex; +use Zend\Validator\StringLength; + +return [ + 'name' => 'dds-form', + 'elements' => [ + 'username' => [ + 'spec' => [ + 'name' => 'username', + 'type' => Text::class, + 'options' => [ + 'label' => 'Name', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'username', + 'readonly' => true, + ], + ], + ], + 'phone' => [ + 'spec' => [ + 'name' => 'phone', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_phone', + ], + 'attributes' => [ + 'required' => false, + 'id' => 'phone', + 'readonly' => true, + ], + ], + ], + 'email' => [ + 'spec' => [ + 'name' => 'email', + 'type' => Email::class, + 'options' => [ + 'label' => 'form_field_email', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'email', + 'readonly' => true, + ], + ], + ], + 'userid' => [ + 'spec' => [ + 'name' => 'userid', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_library_id', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'userid', + 'readonly' => true, + ], + ], + ], + 'division' => [ + 'spec' => [ + 'name' => 'division', + 'type' => Select::class, + 'options' => [ + 'label' => 'form_field_division', + 'disable_inarray_validator' => true, + 'empty_option' => '', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'division', + ], + ], + ], + 'department' => [ + 'spec' => [ + 'name' => 'department', + 'type' => Select::class, + 'options' => [ + 'label' => 'form_field_department', + 'disable_inarray_validator' => true, + ], + 'attributes' => [ + 'required' => true, + 'id' => 'department', + ], + ], + ], + 'inputdepartment' => [ + 'spec' => [ + 'name' => 'inputdepartment', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_department', + ], + 'attributes' => [ + 'required' => false, + 'id' => 'inputdepartment', + ], + ], + ], + 'author' => [ + 'spec' => [ + 'name' => 'author', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_author', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'author', + ], + ], + ], + 'article' => [ + 'spec' => [ + 'name' => 'article', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_title', + ], + 'attributes' => [ + 'required' => false, + 'id' => 'article', + ], + ], + ], + 'journal' => [ + 'spec' => [ + 'name' => 'journal', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_journal', + ], + 'attributes' => [ + 'required' => false, + 'id' => 'journal', + ], + ], + ], + 'issn' => [ + 'spec' => [ + 'name' => 'issn', + 'type' => Text::class, + 'options' => [ + 'label' => 'ISSN', + ], + 'attributes' => [ + 'required' => false, + 'id' => 'issn', + ], + ], + ], + 'publishdate' => [ + 'spec' => [ + 'name' => 'publishdate', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_publishing_date', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'publishdate', + ], + ], + ], + 'number' => [ + 'spec' => [ + 'name' => 'number', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_volume', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'number', + ], + ], + ], + 'pages' => [ + 'spec' => [ + 'name' => 'pages', + 'type' => Text::class, + 'options' => [ + 'label' => 'form_field_pages', + ], + 'attributes' => [ + 'required' => true, + 'id' => 'pages', + ], + ], + ], + 'remarks' => [ + 'spec' => [ + 'name' => 'remarks', + 'type' => Textarea::class, + 'options' => [ + 'label' => 'form_fields_remarks', + ], + 'attributes' => [ + 'required' => false, + 'id' => 'remarks', + ], + ], + ], + 'submit' => [ + 'spec' => [ + 'name' => 'submit', + 'type' => Submit::class, + 'attributes' => [ + 'value' => 'form_button_submit', + 'id' => 'submit', + ], + ], + ], + ], + 'input_filter' => [ + 'division' => [ + 'name' => 'division', + 'required' => true, + 'filters' => [ + StringTrim::class => [ + 'name' => StringTrim::class, + ], + ], + 'validators' => [ + NotEmpty::class => [ + 'name' => NotEmpty::class, + 'options' => [ + NotEmpty::INTEGER => 'dds_division', + NotEmpty::IS_EMPTY => 'dds_division', + NotEmpty::NULL => 'dds_division' + ] + ] + ], + ], + 'journal' => [ + 'name' => 'journal', + 'required' => false, + 'filters' => [ + StringTrim::class => [ + 'name' => StringTrim::class, + ], + ], + 'validators' => [ + StringLength::class => [ + 'name' => StringLength::class, + 'options' => [ + 'max' => 255 + ] + ] + ], + ], + 'issn' => [ + 'name' => 'issn', + 'required' => false, + 'filters' => [ + StringTrim::class => [ + 'name' => StringTrim::class, + ], + ], + 'validators' => [ + StringLength::class => [ + 'name' => StringLength::class, + 'options' => [ + 'max' => 255 + ] + ], + ], + ], + 'email' => [ + 'name' => 'email', + 'required' => true, + 'validators' => [ + EmailAddress::class => [ + 'name' => EmailAddress::class, + ], + ], + ], + 'publishdate' => [ + 'name' => 'publishdate', + 'filters' => [ + StringTrim::class => [ + 'name' => StringTrim::class, + ], + ], + 'validators' => [ + StringLength::class => [ + 'name' => StringLength::class, + 'options' => [ + 'min' => 2, + 'max' => 4, + 'messages' => [ + StringLength::TOO_SHORT => 'dds_error_publishdate_too_short', + StringLength::TOO_LONG => 'dds_error_publishdate_too_long', + ], + ], + ], + Regex::class => [ + 'name' => Regex::class, + 'break_chain_on_failure' => true, + 'options' => [ + 'pattern' => '/^(0|[1-9]\d*)$/', + 'messages' => [ + Regex::NOT_MATCH => 'acquisition_error_publishdate_pattern', + ], + ], + ], + ], + ], + 'pages' => [ + 'name' => 'pages', + 'filters' => [ + StringTrim::class => [ + 'name' => StringTrim::class, + ], + PregReplace::class => [ + 'name' => PregReplace::class, + 'options' => [ + 'pattern' => '/(-|–|—|−)/', + 'replacement' => '-' + ] + ], + ], + 'validators' => [ + StringLength::class => [ + 'name' => StringLength::class, + 'options' => [ + 'min' => 1, + 'messages' => [ + StringLength::TOO_SHORT => 'dds_error_pages_too_short', + ], + ], + ], + Regex::class => [ + 'name' => Regex::class, + 'break_chain_on_failure' => true, + 'options' => [ + 'pattern' => '/^(((0|[1-9]\d*)(-([1-9]\d*)|)|,|;)*)$/', + 'messages' => [ + Regex::NOT_MATCH => 'acquisition_error_pages_pattern', + ], + ], + ], + ], + ], + 'remarks' => [ + 'name' => 'remarks', + 'required' => false, + 'filters' => [ + StringTrim::class => [ + 'name' => StringTrim::class, + ], + ], + 'validators' => [ + StringLength::class => [ + 'name' => StringLength::class, + 'options' => [ + 'max' => 5000 + ] + ], + ], + ], + ], +]; \ No newline at end of file diff --git a/module/finc/config/module.config.php b/module/finc/config/module.config.php index 7673bcd88c9a4de514c7e6cf031600f08fd18083..a5dfdc532872e3cdaab10332afa88f79cfe23231 100644 --- a/module/finc/config/module.config.php +++ b/module/finc/config/module.config.php @@ -2,6 +2,9 @@ namespace finc\Module\Configuration; $config = [ + 'forms' => [ + 'dds-form' => require 'dds-form.php', + ], 'listeners' => [ 'finc\Listener\I18nDataDirListener', ], diff --git a/module/finc/src/finc/AjaxHandler/GetResolverLinks.php b/module/finc/src/finc/AjaxHandler/GetResolverLinks.php index ede2a0813fb502a8cf97cba198d2245d09ecf18f..ab83397f92cb284133e7617a1e522a189ffb2052 100644 --- a/module/finc/src/finc/AjaxHandler/GetResolverLinks.php +++ b/module/finc/src/finc/AjaxHandler/GetResolverLinks.php @@ -31,8 +31,9 @@ */ namespace finc\AjaxHandler; -use VuFind\Resolver\Driver\PluginManager as ResolverManager; use VuFind\Resolver\Connection; +use VuFind\Resolver\Driver\DriverInterface; +use VuFind\Resolver\Driver\PluginManager as ResolverManager; use VuFind\Session\Settings as SessionSettings; use Zend\Config\Config; use Zend\Mvc\Controller\Plugin\Params; @@ -62,7 +63,6 @@ class GetResolverLinks extends \VuFind\AjaxHandler\GetResolverLinks */ protected $resolverConfig; - /** * Constructor * @@ -70,7 +70,7 @@ class GetResolverLinks extends \VuFind\AjaxHandler\GetResolverLinks * @param ResolverManager $pm Resolver driver plugin manager * @param RendererInterface $renderer View renderer * @param Config $config Top-level VuFind configuration (config.ini) - * @param Config $resolver Top-level VuFind configuration (Resolver.ini) + * @param Config $resolver Link Resolver configuration (Resolver.ini) */ public function __construct( SessionSettings $ss, @@ -98,16 +98,17 @@ class GetResolverLinks extends \VuFind\AjaxHandler\GetResolverLinks { $this->disableSessionWrites(); // avoid session write timing bug $openUrl = $params()->fromQuery('openurl', ''); - $requestedResolver = $params()->fromQuery('resolvertype', ''); + $resolver = $params()->fromQuery('resolvertype', ''); $searchClassId = $params()->fromQuery('searchClassId', ''); //$config = $this->getConfig('Resolver'); $resolvers = explode(',', $this->resolverConfig->General->active_resolvers); - if (in_array($requestedResolver, $resolvers) - && isset($this->resolverConfig->$requestedResolver)) { - $resolverType = isset($this->resolverConfig->$requestedResolver->resolver) - ? $this->resolverConfig->$requestedResolver->resolver : 'generic'; + if (in_array($resolver, $resolvers) + && isset($this->resolverConfig->$resolver) + ) { + $resolverType = isset($this->resolverConfig->$resolver->resolver) + ? $this->resolverConfig->$resolver->resolver : 'generic'; if (!$this->pluginManager->has($resolverType)) { return $this->formatResponse( $this->translate("Could not load driver for $resolverType"), @@ -115,64 +116,79 @@ class GetResolverLinks extends \VuFind\AjaxHandler\GetResolverLinks ); } - $resolver = new Connection($this->pluginManager->get($resolverType)); - if (isset($this->resolverConfig->$requestedResolver->resolver_cache)) { - $resolver->enableCache( - $this->resolverConfig->$requestedResolver->resolver_cache + /** + * Loaded Resolver + * + * @var DriverInterface $resolverObject + */ + $resolverObject + = new Connection($this->pluginManager->get($resolverType)); + if (isset($this->resolverConfig->$resolver->resolver_cache)) { + $resolverObject->enableCache( + $this->resolverConfig->$resolver->resolver_cache ); } - $result = $resolver->fetchLinks($openUrl); + $result = $resolverObject->fetchLinks($openUrl); // Sort the returned links into categories based on service type: - $electronic = $print = $services = []; + $electronic = $print = $services = $unknown = []; foreach ($result as $link) { - switch (isset($link['service_type']) ? $link['service_type'] : '') { - case 'getHolding': - $print[] = $link; - break; - case 'getWebService': - $services[] = $link; - break; - case 'getDOI': - // Special case -- modify DOI text for special display: - $link['title'] = $this->translate('Get full text'); - $link['coverage'] = ''; - break; - case 'getFullTxt': - default: - $electronic[] = $link; - break; + switch ($link['service_type'] ?? '') { + case 'getHolding': + $print[] = $link; + break; + case 'getWebService': + $services[] = $link; + break; + case 'getDOI': + // Special case -- modify DOI text for special display: + $link['title'] = $this->translate('Get full text'); + $link['coverage'] = ''; + break; + case 'unknown': + $unknown[] = $link; + break; + case 'getFullTxt': + default: + $electronic[] = $link; + break; } } // Get the OpenURL base: - if (isset($this->resolverConfig->$requestedResolver) - && isset($this->resolverConfig->$requestedResolver->url) + if (isset($this->resolverConfig->$resolver) + && isset($this->resolverConfig->$resolver->url) ) { - // Trim off any parameters (for legacy compatibility -- default config - // used to include extraneous parameters): + // Trim off any parameters (for legacy compatibility + // -- default config used to include extraneous parameters): list($base) = explode( '?', - $this->resolverConfig->$requestedResolver->url + $this->resolverConfig->$resolver->url ); } else { $base = false; } - $moreOptionsLink = $resolver->supportsMoreOptionsLink() - ? $resolver->getResolverUrl($openUrl) : ''; + $moreOptionsLink = $resolverObject->supportsMoreOptionsLink() + ? $resolverObject->getResolverUrl($openUrl) : ''; // Render the links using the view: $view = [ 'openUrlBase' => $base, 'openUrl' => $openUrl, 'print' => $print, 'electronic' => $electronic, 'services' => $services, - 'searchClassId' => $searchClassId, 'resolver' => $requestedResolver, + 'searchClassId' => $searchClassId, 'resolver' => $resolver, 'moreOptionsLink' => $moreOptionsLink ]; + $html = $this->renderer->render('ajax/resolverLinks.phtml', $view); + + // output HTML encoded in JSON object + return $this->formatResponse(compact('html')); } - $html = $this->renderer->render('ajax/resolverLinks.phtml', $view); - // output HTML encoded in JSON object - return $this->formatResponse(compact('html')); + // if we get here, the requested resolver is not configured + return $this->formatResponse( + $this->translate("Resolver $resolver not configured"), + self::STATUS_HTTP_ERROR + ); } } diff --git a/module/finc/src/finc/Controller/Admin/I18nController.php b/module/finc/src/finc/Controller/Admin/I18nController.php index 7052a038b9d04e14d16ef0be2e2c8b4e4e37eafd..b29ba1e16acb2fc1ad1e34c4f2f7eafd220b1345 100644 --- a/module/finc/src/finc/Controller/Admin/I18nController.php +++ b/module/finc/src/finc/Controller/Admin/I18nController.php @@ -62,6 +62,11 @@ class I18nController extends AbstractAdmin */ protected $translations; + /** + * @var string + */ + private $locale; + /** * I18nController constructor. * @@ -92,21 +97,21 @@ class I18nController extends AbstractAdmin { $params = $this->params(); $defaultLocale = array_keys($this->languages)[0]; - $locale = $params->fromQuery('locale', $defaultLocale); + $this->locale = $locale = $params->fromQuery('locale', $defaultLocale); $domain = $params->fromQuery('domain', 'default'); $translations = $this->getTranslations(); $defaultTranslations = $this->getDefaultTranslations(); - foreach (array_keys($translations[$locale]) as $name) { + foreach (array_keys($translations[$this->locale]) as $name) { $selected = $name === $domain; $domains[$name] = compact('name', 'selected'); } $languages = array_map( - function ($lang) use ($locale) { + function ($lang) { $langLocale = $lang['locale']; - $selected = $langLocale === $locale; + $selected = $langLocale === $this->locale; return $lang + compact('selected'); }, $this->languages @@ -135,11 +140,11 @@ class I18nController extends AbstractAdmin $params = $this->params(); $view->setVariables( [ - 'locale' => $locale = $params->fromPost('locale'), - 'language' => $this->languages[$locale]['name'], - 'domain' => $params->fromPost('domain'), - 'token' => $params->fromPost('token'), - 'value' => $params->fromPost('value'), + 'locale' => $locale = $params->fromPost('locale'), + 'language' => $this->languages[$locale]['name'], + 'domain' => $params->fromPost('domain'), + 'token' => $params->fromPost('token'), + 'value' => $params->fromPost('value'), 'defaultValue' => $params->fromPost('default_value') ] ); @@ -186,14 +191,12 @@ class I18nController extends AbstractAdmin protected function loadTranslations(array $dirs) { - foreach (array_keys($this->languages) as $locale) { - foreach ($this->getDomains() as $domain) { - $translations[$locale][$domain] = $this->loadMessages( - $dirs, - $domain, - $locale - ); - } + foreach ($this->getDomains() as $domain) { + $translations[$this->locale][$domain] = $this->loadMessages( + $dirs, + $domain, + $this->locale + ); } return $translations ?? []; diff --git a/module/finc/src/finc/Controller/DocumentDeliveryServiceController.php b/module/finc/src/finc/Controller/DocumentDeliveryServiceController.php index 5393de813a48a1bea286c161a2db546d1390a008..23d91a76fd2b71f3d99fd9731d5a900dabd6f4cf 100644 --- a/module/finc/src/finc/Controller/DocumentDeliveryServiceController.php +++ b/module/finc/src/finc/Controller/DocumentDeliveryServiceController.php @@ -31,6 +31,9 @@ namespace finc\Controller; use VuFind\Exception\Mail as MailException; use finc\Exception\DDS as DDSException; use finc\Mailer\Mailer as Mailer; +use Zend\Form\Element\Select; +use Zend\Form\Form; +use Zend\Http\PhpEnvironment\Request; use Zend\Mail\Address as Address; use Zend\ServiceManager\ServiceLocatorInterface; use Zend\Validator as Validator; @@ -172,7 +175,7 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase /** * Display Feedback home form. * - * @return \Zend\View\Model\ViewModel + * @return mixed * @access public * @throws MailException * @throws DDSException @@ -181,31 +184,17 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase { $content = $this->getContent(); - // Validation - $isError = false; - $fields = ['author', 'division', 'email', 'journal', 'username', - 'number', 'publishdate','pages' - ]; - $departmentfield = ($content['division'] == '15') - ? 'inputdepartment' : 'department'; - array_push($fields, $departmentfield); - - $validator = new Validator\NotEmpty(); - foreach ($fields as $field) { - if (false === $validator->isValid($content[$field])) { - $isError = true; - $error[$field] = ucfirst($field) . ' should not be blank'; + /** @var Form $form */ + /** @var Request $request */ + + //$request = $this->getRequest(); + $form = $this->serviceLocator->get('dds-form'); + if ($this->formWasSubmitted()) { + $form->setData($content); + if (!$form->isValid()) { + return $this->createDDSViewModel($content); } } - $validator = new Validator\EmailAddress(); - if (false === $validator->isValid($content['email'])) { - $isError = true; - $error['email'] = 'The email is not valid '; - } - if (true === $isError) { - $content['error'] = (object) $error; - return $this->createDDSViewModel($content); - } // Prepare Email Template $body = $this->buildEmailTemplates($content); @@ -406,9 +395,8 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase // Define obligated fields // obligated at least one of ss or jt if (empty($fields['issn']) && empty($fields['journal'])) { - throw new DDSException( - 'At least issn or title of journal is necessary for an order at - Subito service.' + $this->flashMessenger()->addMessage( + 'DDS::dds_issn_journal', 'error' ); } // all fields of vol, apy, pg @@ -416,8 +404,8 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase empty($fields['publishdate']) || empty($fields['pages']) ) { - throw new DDSException( - 'Pages, publish date and volume are binding statements., ' + $this->flashMessenger()->addMessage( + 'DDS::dds_binding_statements', 'error' ); } // build subito url @@ -443,14 +431,24 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase */ protected function createDDSViewModel($params = null) { - $view = $this->createViewModel(); - // Assign vars to view. + /** @var Form $form */ + $form = $this->serviceLocator->get('dds-form'); + foreach ($params as $key => $value) { - $view->$key = $value; + $elem = $form->get($key); + $elem->setValue($value); } + + $action = $this->url()->fromRoute('dds-email'); + $form->setAttribute('action', $action); + $form->prepare(); + + $view = $this->createViewModel(); + $view->setVariables(compact('form')); // Assign vars for select menu to view. + $elemSdivision = $form->get('division'); + $elemSdivision->setValueOptions($this->getDivisions()); $view->departments = $this->getDepartments(); - $view->divisions = $this->getDivisions(); $view->loadForm = true; $view->setTemplate('documentdeliveryservice/home'); return $view; @@ -485,7 +483,7 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase $post = []; if ($this->getRequest()->isPost()) { $getPost = $this->getRequest()->getPost()->toArray(); - $post = isset($getPost['subito']) ? $getPost['subito'] : []; + $post = !empty($getPost) ? $getPost : []; } // populate the view with data given by User catalog account @@ -698,7 +696,7 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase foreach ($t as $arr) { // Build and normalize dataset of division if (false === array_key_exists($arr['fakultaetid'], $this->division)) { - $this->division[$arr['fakultaetid']] = $arr['fakultaet']; + $this->division[$arr['fakultaetid']] = html_entity_decode($arr['fakultaet']); } // Build options dataset $this->department[$arr['fakultaetid']][$arr['institutid']] @@ -966,13 +964,15 @@ class DocumentDeliveryServiceController extends \VuFind\Controller\AbstractBase } $responseArray = $this->parseJsonAsArray($response->getContent()); - if (array_key_exists('error', $responseArray)) { + if ($responseArray !== null && array_key_exists('error', $responseArray)) { $message = $responseArray['error']; } } else { $message = 'HTTP status ' . $response->getStatusCode() . ' received'; } - throw new DDSException($message); + if (!empty($message)) { + throw new DDSException($message); + } } return $this->parseJsonAsArray($response->getBody()); diff --git a/module/finc/src/finc/Cover/Loader.php b/module/finc/src/finc/Cover/Loader.php index fe853e9f2e70b56c78ce7c19e6c80b9da8f3e06b..191c033eedfb322a6705907c526131a7157a544e 100644 --- a/module/finc/src/finc/Cover/Loader.php +++ b/module/finc/src/finc/Cover/Loader.php @@ -59,7 +59,7 @@ class Loader extends \VuFind\Cover\Loader public function loadUnavailable() { $this->hasLoadedUnavailable = true; - if ($this->config->Content->useCoverFallbacksOnFail) { + if ($this->config->Content->useCoverFallbacksOnFail && !empty($this->localFile)) { file_put_contents($this->localFile, self::EMPTY_IMAGE_CONTENT); } return parent::loadUnavailable(); diff --git a/module/finc/src/finc/ILS/Connection.php b/module/finc/src/finc/ILS/Connection.php index 4ddbdddb7def752e7bd4613feefb22c14e280a71..a7f05faf35fdc579c430ccbc362c746858fe0187 100644 --- a/module/finc/src/finc/ILS/Connection.php +++ b/module/finc/src/finc/ILS/Connection.php @@ -91,4 +91,87 @@ class Connection extends \VuFind\ILS\Connection implements TranslatorAwareInterf } return $response; } + + /** + * Check ILLRequests + * + * A support method for checkFunction(). This is responsible for checking + * the driver configuration to determine if the system supports ILL requests. + * + * @param array $functionConfig The ILL request configuration values + * @param array $params An array of function-specific params (or null) + * + * @return mixed On success, an associative array with specific function keys + * and values either for placing requests via a form; on failure, false. + */ + protected function checkMethodILLRequests($functionConfig, $params) + { + if ( + method_exists($this, 'getMyILLRequests') + && + isset($functionConfig['daiaILLpattern']) + ) { + return ['daiaILLpattern' => $functionConfig['daiaILLpattern']]; + } + + return false; + } + + + /** + * Get access to the driver object. + * Extends parent function with a bypass of failover on InitException + * + * @param bool $init Should we initialize the driver (if necessary), or load it + * "as-is"? + * + * @throws \Exception + * @throws InitException + * @return object + */ + public function getDriver($init = true) + { + if (null === $this->driver) { + $this->setDriver($this->driverManager->get($this->config->driver)); + } + if (!$this->driverInitialized && $init) { + try { + $this->initializeDriver(); + } catch (InitException $e) { + throw $e; + } catch (\Exception $e) { + if (!$this->failOverToNoILS()) { + throw $e; + } + } + } + return $this->driver; + } + + /** + * Get Hidden Login Mode + * + * This is responsible for indicating whether login should be hidden. + * + * Extends parent function with a bypass of failover on InitException + * + * @throws \Exception + * @throws InitException + * @return bool true if the login should be hidden, false if not + */ + public function loginIsHidden() + { + // Graceful degradation -- return false if no method supported. + try { + return $this->checkCapability('loginIsHidden') + ? $this->getDriver()->loginIsHidden() : false; + } catch (InitException $e) { + throw $e; + } catch (\Exception $e) { + if ($this->failOverToNoILS()) { + return call_user_func_array([$this, __METHOD__], func_get_args()); + } + throw $e; + } + } } diff --git a/module/finc/src/finc/ILS/Driver/FincILS.php b/module/finc/src/finc/ILS/Driver/FincILS.php index fd6dee3b7a9956b03b1e90bff2d47e4b69205f09..7f8f971642d890cfab5d37eb0ae675cc449a01a1 100644 --- a/module/finc/src/finc/ILS/Driver/FincILS.php +++ b/module/finc/src/finc/ILS/Driver/FincILS.php @@ -108,6 +108,22 @@ class FincILS extends PAIA implements LoggerAwareInterface */ protected $isil; + /** + * Regex to be used in getMyILLRequests, + * finds item IDs denoting inter library loan requests + * + * @var string + */ + protected $illItemPattern; + + /** + * Regex to be used in getMyILLRequests, + * finds item labels denoting inter library loan requests + * + * @var string + */ + protected $illLabelPattern; + /** * Connection timeout in seconds used for _testILSConnection() * @@ -223,6 +239,16 @@ class FincILS extends PAIA implements LoggerAwareInterface $this->ilsTestTimeout = isset($this->config['General']) && isset($this->config['General']['ilsTestTimeout']) ? $this->config['General']['ilsTestTimeout'] : 90; + + // set filter for reserved item IDs at ILL request + $this->illItemPattern = + (isset($this->config['ILLRequests']['itemPattern'])) + ? $this->config['ILLRequests']['itemPattern'] : null; + + // set filter for reserved item labels at ILL request + $this->illLabelPattern = + (isset($this->config['ILLRequests']['labelPattern'])) + ? $this->config['ILLRequests']['labelPattern'] : null; } /** @@ -421,7 +447,7 @@ class FincILS extends PAIA implements LoggerAwareInterface $retval = array_fill(0, count($ids), false); foreach ($new['first_results'] as $record) { /** @var \finc\RecordDriver\SolrDefault $record */ - $callNumbers = $record->getField($idType); + $callNumbers = (array)$record->getField($idType); $matches = array_intersect($ids, $callNumbers); foreach ($matches as $number => $match) { /* map identifier to solr id */ @@ -632,6 +658,14 @@ 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; } @@ -1696,4 +1730,34 @@ class FincILS extends PAIA implements LoggerAwareInterface // overriden in FincLibero return $limitations; } + + /** + * Customized getMyILLRequests, relies on PAIA-URI pattern + * @param array $patron + * @return array + */ + public function getMyILLRequests($patron) + { + // filters for getMyILLRequests are: + // document.item = URI has to match config pattern + + if (!empty($this->illItemPattern)) { + $filter['regex']['item'] = $this->illItemPattern; + } + + if (!empty($this->illLabelPattern)) { + // filter out some item according their status label + // cf. #15214 + $filter['regex']['label'] = $this->illLabelPattern; + } + + if (!empty($filter)) { + // get items-docs for given filters + $items = $this->paiaGetItems($patron, $filter); + + return $this->mapPaiaItems($items, 'myHoldsMapping'); + } + + return []; + } } diff --git a/module/finc/src/finc/ILS/Driver/FincLibero.php b/module/finc/src/finc/ILS/Driver/FincLibero.php index 8c552e7387710567d5bbd0afafb0fb8b81e6b827..5aa9a5825be8626c5223f09887b1cf7736ce4b3d 100644 --- a/module/finc/src/finc/ILS/Driver/FincLibero.php +++ b/module/finc/src/finc/ILS/Driver/FincLibero.php @@ -27,6 +27,7 @@ * @link http://vufind.org/wiki/vufind2:building_an_ils_driver Wiki */ namespace finc\ILS\Driver; +use finc\ILS\InitException; use VuFind\I18n\Translator\TranslatorAwareTrait; use VuFind\I18n\Translator\TranslatorAwareInterface, VuFind\Exception\ILS as ILSException; @@ -122,8 +123,136 @@ class FincLibero extends FincILS implements TranslatorAwareInterface 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; + + $this->setMemberFromConfig('boundItemPattern', 'General', 'bound_item_id_pattern'); + $this->setMemberFromConfig('boundItemLabelPattern', 'General', + 'bound_item_label_pattern'); + + // Get the base URI to extend departments. + $this->setMemberFromConfig( + 'departmentLocationBase', 'General', 'departmentLocationBase', + "No departmentLocationBase defined in FincLibero.ini." + ); + + // get the URIs for the limitations that are marking an item that a hold can + // be placed on + $this->setMemberFromConfig( + 'titleHoldLimitations', + 'General', + 'titleHoldLimitations', + "No Limitations defined for action titleHold.", + 'titleHoldLimitations' + ); + + // get the pickUpLocationPatterns configured in ILS ini and being used for + // filtering limitations used for pickuplocations + $this->setMemberFromConfig( + 'pickUpLocationPatterns', + 'General', + 'pickUpLocationPatterns', + "No pickUpLocationPatterns defined in FincLibero.ini." + ); + + // get the URIs for the limitations that are marking an item that a storage + // retrieval request can be placed on + $this->setMemberFromConfig( + 'requestableLimitations', + 'General', + 'requestableLimitations', + "No Limitations defined for action Storage Retrieval Request.", + 'requestableLimitations' + ); + + // get the URIs for the limitations that are marking an item that a hold can + // be placed on + $this->setMemberFromConfig( + 'holdableLimitations', + 'General', + 'holdableLimitations', + "No Limitations defined for action Hold.", + 'holdableLimitations' + ); + + // get the URIs for the limitations that are marking an item that a recall can + // be placed on + $this->setMemberFromConfig( + 'recallableLimitations', + 'General', + 'recallableLimitations', + "No Limitations defined for action Recall.", + 'recallableLimitations' + ); + + // get URIs of limitations that will identify the item for being bound to + // another item + $this->setMemberFromConfig( + 'awlLimitations', + 'General', + 'awlLimitations', + "No awlLimitations defined.", + 'awlLimitations' + ); + + // get the URIs identifying records for stack views + $this->setMemberFromConfig( + 'stackURIs', + 'General', + 'stackURIs', + "No stack URIs defined." + ); + + // get the URIs identifying records for reading room views + $this->setMemberFromConfig( + 'readingRoomURIs', + 'General', + 'readingRoomURIs', + "No reading room URIs defined." + ); + } + + /** + * Helper function for @see FincLibero::init() + * Set some member variables based on Config entries from FincILS.ini or + * FincLibero.ini. + * If the given config key (in given section) is set, the member variable will be + * initialized with the value(s) from that key. + * Otherwise (not set): + * When the setting is required via the [RequiredConfig] section AND a debug message is + * given, this message will be added to the debug log. + * With the last parameter $configuredLimitationName, the config entry will be added + * to the set of configured limitations under the given name + * + * @param string $memberName name of class member to be set + * @param string $configSection section from INI file to look up (e.g. 'General' for section [General]) + * @param string $configKey config key within that section + * @param string $debugMessage message to add to debug log on fail if the config is set required + * @param string $configuredLimitationName name of limitation type to be used in setConfiguredLimitation() + */ + protected function setMemberFromConfig( + $memberName, + $configSection, + $configKey, + $debugMessage = '', + $configuredLimitationName = '' + ) { + if ( + !isset($this->config[$configSection]) + || + !isset($this->config[$configSection][$configKey]) + ) { + if ( + !empty($debugMessage) + && + ($this->config['RequiredConfig'][$configSection][$configKey] ?? false) + ) { + throw new InitException($debugMessage); + } + } else { + $this->{$memberName} = $this->config[$configSection][$configKey]; + if (!empty($configuredLimitationName)) { + $this->setConfiguredLimitation($configuredLimitationName); + } + } } /** @@ -136,6 +265,121 @@ class FincLibero extends FincILS implements TranslatorAwareInterface return preg_quote(substr($this->daiaIdPrefix, 0, strpos($this->daiaIdPrefix, ':')+1)); } + /** + * Parse an array with DAIA status information. + * Copies \VuFind\ILS\Driver\DAIA::parseDaiaArray to enable finc-specific + * titleHoldLogic + * + * @param string $id Record id for the DAIA array. + * @param array $daiaArray Array with raw DAIA status information. + * + * @return array Array with VuFind compatible status information. + */ + protected function parseDaiaArray($id, $daiaArray) + { + $doc_id = null; + $doc_href = null; + + if (isset($daiaArray['id'])) { + $doc_id = $daiaArray['id']; + } + + if (isset($daiaArray['href'])) { + // url of the document (not needed for VuFind) + $doc_href = $daiaArray['href']; + } + + if (isset($daiaArray['message'])) { + // log messages for debugging + $this->logMessages($daiaArray['message'], 'document'); + } + + // if one or more items exist, iterate and build result-item + if (isset($daiaArray['item']) && is_array($daiaArray['item'])) { + $isTitleHold = null; + $isTitleHoldable = $this->hasTitleHolds(); + $number = 0; + foreach ($daiaArray['item'] as $item) { + // if it is a title-holdable record, the first item is a dummy + // that delivers the title hold information and MUST NOT be + // forwarded as an actual piece of availability information + // instead, all following items MUST be marked as title-holdable, + // to enable the frontend to display an adequate link + if ($isTitleHoldable && !$isTitleHold) { + if ($isTitleHold = $this->isTitleHold($item)) { + $titleHoldId = $item['id']; + continue; + } + } + $result_item = []; + $result_item['id'] = $id; + + // custom DAIA field + $result_item['doc_id'] = $doc_id; + if ( + $isTitleHold + && + ( + !isset($this->noTitleHoldStatuses) + || + empty($this->noTitleHoldStatuses) + || + !in_array($item['localIlsStatus'],$this->noTitleHoldStatuses) + ) + ) { + $result_item['item_id'] = $titleHoldId; + } else { + $result_item['item_id'] = $item['id']; + } + + // custom DAIA field used in getHoldLink() + $result_item['ilslink'] + = (isset($item['href']) ? $item['href'] : $doc_href); + if ($isTitleHold) { + $result_item['addTitleHoldLink'] = TRUE; + } + + // count items + $number++; + $result_item['number'] = $this->getItemNumber($item, $number); + + // set default value for barcode + $result_item['barcode'] = $this->getItemBarcode($item); + + // set default value for reserve + $result_item['reserve'] = $this->getItemReserveStatus($item); + + // get callnumber + $result_item['callnumber'] = $this->getItemCallnumber($item); + + // get location + $result_item['location'] = $this->getItemDepartment($item); + + // custom DAIA field + $result_item['locationid'] = $this->getItemDepartmentId($item); + + // get location link + $result_item['locationhref'] = $this->getItemDepartmentLink($item); + + // custom DAIA field + $result_item['storage'] = $this->getItemStorage($item); + + // custom DAIA field + $result_item['storageid'] = $this->getItemStorageId($item); + + // custom DAIA field + $result_item['storagehref'] = $this->getItemStorageLink($item); + + // status and availability will be calculated in own function + $result_item = $this->getItemStatus($item) + $result_item; + + // add result_item to the result array + $result[] = $result_item; + } // end iteration on item + } + return $result; + } + /** * FincLibero specific overrides of PAIA methods */ @@ -295,6 +539,28 @@ class FincLibero extends FincILS implements TranslatorAwareInterface } } + /** + * FincLibero specific override: we need to manually update the cat_password in + * the session as we do not save passwords in the database and therefore the + * ILSAuthenticator will use the session data instead. + * + * @param array $details Array with patron information, newPassword and + * oldPassword. + * + * @return array An array with patron information. + */ + public function changePassword($details) + { + $retval = parent::changePassword($details); + + if ($retval == ['success' => true, 'status' => 'Successfully changed']) { + $session = $this->getSession(); + $session->cat_password = $details['newPassword']; + } + + return $retval; + } + /** * PAIA helper function to map session data to return value of patronLogin() * @@ -341,6 +607,20 @@ class FincLibero extends FincILS implements TranslatorAwareInterface return $details; } + /** + * Returns the value for "callnumber" in VuFind getStatus/getHolding array + * + * @param array $item Array with DAIA item data + * + * @return string + */ + protected function getItemCallnumber($item) + { + return isset($item['label']) && !empty($item['label']) + ? $item['label'] + : ''; + } + /** * FincLibero specific overrides of DAIA methods */ @@ -356,31 +636,71 @@ class FincLibero extends FincILS implements TranslatorAwareInterface { $return = parent::getItemStatus($item); + $return['item_notes'] = $this->getItemNotes($return, $item); + $return['awlRecordId'] = $this->getBoundItemId($item); // is this item bound with another item? if ($return['awlRecordId'] != null) { + static $awlStatuses = []; // overwrite any existing link settings as we need to order this item // via the bound item $return['addLink'] = $return['addStorageRetrievalRequestLink'] = $return['addILLRequestLink'] = $return['addEmailHoldLink'] = false; - $return['awlRecordStatus'] = - current($this->getStatus($return['awlRecordId'])); + if (isset($awlStatuses[$return['awlRecordId']])) { + $return['awlRecordStatus'] = $awlStatuses[$return['awlRecordId']]; + } else { + // fix for infinite recursion due to circularly bound items + $awlStatuses[$return['awlRecordId']] = 'pending'; + $return['awlRecordStatus'] = + $awlStatuses[$return['awlRecordId']] = + current($this->getStatus($return['awlRecordId'])); + } } + $return['service_type'] = $this->_reduceServices($return['services']); + + return $return; + } + + /** + * Helper function for getItemStatus(). + * Gather all item notes from current (parent) result and DAIA item + * + * @param array $return intermediate result from parent::getItemStatus() + * @param array $item DAIA item + * @return array 'item_notes' part for result + */ + protected function getItemNotes($return, $item) + { // add all item specific information from DAIA field about to item_notes // (https://intern.finc.info/issues/7863) - $about = (isset($item['about'])) ? [$item['about']] : []; - - $return['item_notes'] = array_unique( + $about = $item['about'] ?? []; + return array_unique( array_merge( - (array) $return['status'], + (array)$return['status'], $return['item_notes'], - $about + (array) $about ) ); - return $return; + } + + /** + * Helper function for 'service_type' entry in getItemStatus + * + * @param array $services actually present services in DAIA item + * @return false|int|mixed preferred service if present or 0 + */ + protected function _reduceServices($services) + { + $prio = [ + 'loan', + 'presentation', + ]; + $res = array_intersect($prio, $services); + if (empty($res)) return 0; + else return current($res); } /** @@ -595,6 +915,31 @@ class FincLibero extends FincILS implements TranslatorAwareInterface return $this->mapPaiaItems($items, 'myHoldsMapping'); } + /** + * This method queries the ILS for a patron's current storage retrieval requests. + * Returns items with properties: + * - document.status = 2 (ordered) + * - document.storage and document.storageid do have a certain location (Magazin, but not Lesesaal) + * + * note: stackURIs musst be configured in FincLibero.ini as required + * + * @param array $patron Array returned from patronLogin() + * + * @return array + */ + public function getMyStorageRetrievalRequests($patron) + { + $filter = [ + 'status' => [2], + 'storageid' => $this->stackURIs, + 'regex' => ['item' => "/^(" . $this->getDaiaIdPrefixNamespace() . ").*$/"] + ]; + // get items-docs for given filters + $items = $this->paiaGetItems($patron, $filter); + + return $this->mapPaiaItems($items, 'myStorageRetrievalRequestsMapping'); + } + /** * Customized getMyTransactions for FincLibero to return items with properties: * - document.status = 3 (held) @@ -817,6 +1162,17 @@ class FincLibero extends FincILS implements TranslatorAwareInterface return null; } + /** + * May there be Title holdable items in this instance? + * Inherited FincLibero drivers should set see $titleHoldLimitations + * to achieve this + * + * @return bool + */ + public function hasTitleHolds() { + return isset($this->titleHoldLimitations); + } + /** * Place Title Hold * @@ -916,4 +1272,47 @@ class FincLibero extends FincILS implements TranslatorAwareInterface { return ''; } + + /** + * This method returns a list of locations where a user may collect a hold. + * + * @param array $patron Patron array returned by patronLogin method + * @param array $details Hold information array similar to placeHold's input + * @return array Array of associative arrays containing these keys: + * locationID - A pick up location id or code (string) + * locationDisplay – The text to display for the location (string) + * + * @link https://vufind.org/wiki/development:plugins:ils_drivers#getpickuplocations + */ + public function getPickUpLocations($patron = [], $details = []) + { + // use departments of Wachtl, not DAIA + if ($this->config['General']['useWachtlPickupLocations'] ?? false) { + return $this->getWachtlPickUpLocations($details); + } + + // use location and locationid, not customData of DAIA + if ($this->config['General']['useDaiaLocationId'] ?? false) { + return parent::getPickUpLocations($patron, $details); + } + + // use DAIA customData + if ($details != [] && isset($details['id'])) { + $statusItems = $this->getStatus($details['id']); + foreach ($statusItems as $statusItem) { + if ($statusItem['item_id'] == $details['item_id']) { + // our pickUpLocations are stored in the customData array upon + // processing the DAIA availability information + if (isset($statusItem['customData']) + && isset($statusItem['customData']['pickUpLocations']) + ) { + return $statusItem['customData']['pickUpLocations']; + } else { + return $this->stackURIs ?? []; + } + } + } + } + return []; + } } diff --git a/module/finc/src/finc/ILS/Driver/LiberoWachtlTrait.php b/module/finc/src/finc/ILS/Driver/LiberoWachtlTrait.php index 6135ea641d59fa38bd564fea19572276f6edb3af..21fcdc09c3ff203ea58b8ae0d49c39ecd2a36d2d 100644 --- a/module/finc/src/finc/ILS/Driver/LiberoWachtlTrait.php +++ b/module/finc/src/finc/ILS/Driver/LiberoWachtlTrait.php @@ -344,67 +344,6 @@ trait LiberoWachtlTrait return $bool; } - /** - * Change values of users profile. - * - * @param array $inval Associative array of key => value. Keys are: - * - memberCode : User ID returned by patronLogin - * - street : street and number - * - additional : optional address value - * - city : city/village - * - zipCode : location zip code - * - emailAddress : email address - * - reason : reason of change - * @param array $patron Patron data - * - * @return boolean true OK, false FAIL - * @access public - * @throws \Exception Throws ILSException - */ - public function setMyProfile($inval, $patron) - { - $map = self::profileDataMapper(true); - - $params = $this->getLiberoWachtlRequestParams(); - $params['memberCode'] = $patron['cat_username']; - $params['password'] = $patron['cat_password']; - - $data = []; - if (is_array($inval) && (count($inval) > 0)) { - foreach ($inval as $k => $v) { - if (isset($map[$k])) { - $data[$map[$k]] = $v; - } else { - $data[$k] = $v; - } - } - } - - $params = array_merge($params, $data); - - try { - $result = $this->httpService->get( - $this->getLiberoWachtlUrl() .'setMyProfile.jsp', - $params, - null, - $this->getLiberoWachtlRequestHeaders() - ); - } catch (\Exception $e) { - throw new ILSException($e->getMessage()); - } - - if (!$result->isSuccess()) { - // log error for debugging - $this->debug( - 'HTTP status ' . $result->getStatusCode() . - ' received' - ); - return false; - } - - return $this->getLiberoWachtlResultBool($result); - } - /** * Returns Array with profile fields that are never allowed to be edited * @@ -503,6 +442,49 @@ trait LiberoWachtlTrait return $this->getLiberoWachtlResult($result, 'getMyProfile'); } + /** + * This method returns a list of locations where a user may collect a hold. + * + * @param array $details Hold information array similar to placeHold's input - needed for overriden method in de_zi4 + * + * @return array $locations Array of associative arrays containing these keys: + * locationID - A pick up location id or code (string) + * locationDisplay – The text to display for the location (string) + * @throws \Exception + * @throws \ILSException + * + * @link https://vufind.org/wiki/development:plugins:ils_drivers#getpickuplocations + */ + public function getWachtlPickUpLocations($details = []) + { + $locations = []; + + try { + $client = $this->httpService->createClient( + $this->baseUrl . + $this->config['General']['liberoDbName'] . + '/departments' + ); + $result = $client->send(); + } catch (\Exception $e) { + throw new ILSException($e->getMessage()); + } + + if ($result->isSuccess()) { + $arr = json_decode($result->getBody(), true); + $i = 0; + foreach ($arr as $code => $name) { + $locations[] + = ['locationID' => $code, 'locationDisplay' => $name] + ; + } + } else { + $this->debug( + "No list of pickup locations found." + ); + } + return $locations; + } /** * This method sends a PIN changing request to the LiberoWachtl. @@ -677,6 +659,9 @@ trait LiberoWachtlTrait $this->getLiberoWachtlRequestHeaders() ); } catch (\Exception $e) { + $this->debug( + 'Error loading loan history from LiberoWachtl: ' . $e->getMessage() + ); throw new ILSException($e->getMessage()); } if (!$result->isSuccess()) { diff --git a/module/finc/src/finc/View/Helper/Root/HeadTitle.php b/module/finc/src/finc/ILS/InitException.php similarity index 56% rename from module/finc/src/finc/View/Helper/Root/HeadTitle.php rename to module/finc/src/finc/ILS/InitException.php index c4036a17429c5da292fb5e574bf9f40d53de3588..7e7a692cc70717ea3e4b107c33806966381ced22 100644 --- a/module/finc/src/finc/View/Helper/Root/HeadTitle.php +++ b/module/finc/src/finc/ILS/InitException.php @@ -1,10 +1,9 @@ <?php /** - * Head Title view helper - * + * Exception thrown in FincLibero::init() * PHP version 7 * - * Copyright (C) Leipzig University Library 2020. + * 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, @@ -20,39 +19,22 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * @category VuFind - * @package View_Helpers + * @package ILS_Drivers * @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/wiki/development Wiki + * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki */ -namespace finc\View\Helper\Root; - -use Zend\View\Helper\HeadTitle as BaseHelper; +namespace finc\ILS; /** - * Head Title view helper + * Exception thrown in FincLibero::init() * * @category VuFind - * @package View_Helpers + * @package ILS_Drivers * @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/wiki/development Wiki + * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki */ -class HeadTitle extends BaseHelper +class InitException extends \Exception { - protected $headerSuffix; - - public function __construct($headerSuffix = '') - { - parent::__construct(); - if (!empty($headerSuffix)) { - $this->headerSuffix = ' - '.$headerSuffix; - } - } - - public function __invoke($title = null, $setType = null) - { - $title = $title ? : $this->headerSuffix; - return parent::__invoke($title, $setType); - } } diff --git a/module/finc/src/finc/RecordDriver/SolrDico.php b/module/finc/src/finc/RecordDriver/SolrDico.php index c18b6323c5f9d13acae3e22426ed6bc5b51dda0b..f534a67a14fc65bfef7d66058de4f478a8e9d589 100644 --- a/module/finc/src/finc/RecordDriver/SolrDico.php +++ b/module/finc/src/finc/RecordDriver/SolrDico.php @@ -124,7 +124,7 @@ class SolrDico extends SolrDefault * Returns the object'S licence information from the full record * @return array */ - public function getLicenceInfo() + public function getLicense() { $prefix = "object_rights_statement_"; $fields = [ diff --git a/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php b/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php index c17bd5e99f6029bf368c46cdf01dccda9e2e53d8..fd921067791bc71bb7c91e914ec67f5ff0c780ee 100644 --- a/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php +++ b/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php @@ -127,7 +127,7 @@ trait SolrMarcFincTrait foreach ($fieldsToCheck as $field => $subfields) { $urls = $this->getMarcRecord()->getFields($field); - $resultsPerIsil = []; + $resultsPerIndicator2 = []; if ($urls) { foreach ($urls as $url) { $isil = $url->getSubfield('9'); @@ -172,7 +172,7 @@ trait SolrMarcFincTrait } } - $resultsPerIsil[$isil][] = [ + $resultsPerIndicator2[$indicator2][$isil][] = [ 'url' => $address, 'desc' => $desc, 'indicators' => $indicator1 . $indicator2, @@ -188,14 +188,19 @@ trait SolrMarcFincTrait } } - $subresult = $this->extractUrls($resultsPerIsil); + $subresult = array_map([$this, 'extractUrls'], $resultsPerIndicator2); if (!empty($subresult)) { - foreach ($subresult as $current) { - // If entry doesn't exist so far write - // to return variable. - if (!in_array($current, $retVal)) { - $retVal[] = $current; + foreach ($subresult as $perIndicator2) { + if (empty($perIndicator2)) { + continue; + } + foreach ($perIndicator2 as $current) { + // If entry doesn't exist so far write + // to return variable. + if (!in_array($current, $retVal)) { + $retVal[] = $current; + } } } } @@ -399,6 +404,22 @@ trait SolrMarcFincTrait return $retval; } + public function getDOI () { + + $allIdentifiers = $this->getOtherIdentifiers(); + $dois = $allIdentifiers['doi'] ?? []; + if (!empty($dois)) { + return current($dois); + } + $urls = $this->getURLs(); + foreach ($urls as $url) { + if (strpos($url['url'],'https://doi.org/') === 0) { + return substr($url['url'],16); + } + } + return null; + } + /** * Get an array of instrumentation notes taken from the local data * of the Petrucci music library subfield 590b @@ -417,7 +438,11 @@ trait SolrMarcFincTrait */ public function getISSNs() { - return $this->getFieldArray('022', ['a']); + $fromMarc = $this->getFieldArray('022', ['a']); + if (empty($fromMarc)) { + return parent::getISSNs(); + } + return $fromMarc; } /** @@ -547,6 +572,26 @@ trait SolrMarcFincTrait return count($array) ? array_pop($array) : ''; } + /** + * Get an array of title details with information from MARC field 249a. + * + * @return array + */ + public function getTitleDetails249a() + { + $titleDetails = []; + + if ($field = $this->getMarcRecord()->getField('249')) { + if ($subfields = $field->getSubfields('a')) { + foreach ($subfields as $subfield) { + $titleDetails[] = $subfield->getData(); + } + } + } + + return array_unique($titleDetails); + } + /** * Get an array of title detail lines with original notations combining * information from MARC field 245 and linked content in 880. @@ -2476,16 +2521,20 @@ trait SolrMarcFincTrait * @return array|null */ public function getLicense() { - if ($field = $this->getMarcRecord()->getField('540')) { + $retval = null; + if ($licenses = $this->getMarcRecord()->getFields('540')) { $retval = []; - foreach (['name' => 'a', 'url' => 'u'] as $key => $sub_name) { - if ($line = $field->getSubfield($sub_name)) { - $retval[$key] = $line->getData(); + $i = 0; + foreach ($licenses as $license) { + foreach (['code' => 'a', 'url' => 'u', 'text' => 'f'] as $key => $sub_name) { + if ($line = $license->getSubfield($sub_name)) { + $retval[$i][$key] = $line->getData(); + } } + $i++; } - return $retval; } - return null; + return $retval; } /** diff --git a/module/finc/src/finc/RecordTab/HierarchyTree.php b/module/finc/src/finc/RecordTab/HierarchyTree.php index cfc6a02c43cc86cde7eb86e3cc1a79e0bcab68a3..56133533858816cab39174a1e310b1cf762716c5 100644 --- a/module/finc/src/finc/RecordTab/HierarchyTree.php +++ b/module/finc/src/finc/RecordTab/HierarchyTree.php @@ -51,17 +51,31 @@ class HierarchyTree extends \VuFind\RecordTab\HierarchyTree else return 'hierarchy_tree'; } + public function isActive() + { + $trees = $this->getTreeList(); + if (empty($trees)) { + return false; + } + foreach ($trees as $key => $value) { + if ($key !== $this->driver->getUniqueID()) { + return true; + } + } + if ($this->driver->getChildRecordCount() > 0) { + return true; + } + return false; + } + /** - * {@inheritdoc} - * Returns different descriptions according to record type - * @return string + * Can this tab be loaded via AJAX? + * + * @return bool */ -/* public function isActive() + public function supportsAjax() { - return ( - $this->getRecordDriver()->tryMethod('isSingleElementHierarchyRecord') - ? false : parent::isActive() - ); + // No, special width adjustment needed. + return true; } -*/ } diff --git a/module/finc/src/finc/Resolver/Driver/Ezb.php b/module/finc/src/finc/Resolver/Driver/Ezb.php index 49010ca04e3bcf9c791a0fca8348f8aa7686333e..f5bd012fa0ae0c8fbabf8fb33386cd58d63dfb25 100644 --- a/module/finc/src/finc/Resolver/Driver/Ezb.php +++ b/module/finc/src/finc/Resolver/Driver/Ezb.php @@ -6,9 +6,9 @@ * http://services.dnb.de/fize-service/gvr/full.xml * * API documentation is available at - * http://www.zeitschriftendatenbank.de/services/schnittstellen/journals-online-print/ + * http://www.zeitschriftendatenbank.de/services/journals-online-print * - * PHP version 5 + * PHP version 7 * * Copyright (C) Markus Fischer, info@flyingfischer.ch * @@ -38,8 +38,8 @@ namespace finc\Resolver\Driver; use DOMDocument; use DOMXpath; -use \VuFind\I18n\Translator\TranslatorAwareInterface as TranslatorAwareInterface; -use \VuFind\Resolver\Driver\AbstractBase; +use VuFind\I18n\Translator\TranslatorAwareInterface as TranslatorAwareInterface; +use VuFind\Resolver\Driver\Ezb as Base; /** * EZB Link Resolver Driver @@ -51,10 +51,10 @@ use \VuFind\Resolver\Driver\AbstractBase; * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:plugins:link_resolver_drivers Wiki */ -class Ezb extends AbstractBase implements TranslatorAwareInterface +class Ezb extends Base implements TranslatorAwareInterface { use \VuFind\I18n\Translator\TranslatorAwareTrait; - use ResolverTrait; + use \finc\Resolver\Driver\EzbTrait; /** * Base URL for link resolver @@ -101,38 +101,6 @@ class Ezb extends AbstractBase implements TranslatorAwareInterface */ public function fetchLinks($openURL) { - // Unfortunately the EZB-API only allows OpenURL V0.1 and - // breaks when sending a non expected parameter (like an ISBN). - // So we do have to 'downgrade' the OpenURL-String from V1.0 to V0.1 - // and exclude all parameters that are not compliant with the EZB. - - // Parse OpenURL into associative array: - $tmp = explode('&', $openURL); - $parsed = []; - - foreach ($tmp as $current) { - $tmp2 = explode('=', $current, 2); - $parsed[$tmp2[0]] = $tmp2[1]; - } - - // Downgrade 1.0 to 0.1 - if ($parsed['ctx_ver'] == 'Z39.88-2004') { - $openURL = $this->downgradeOpenUrl($parsed); - } - - - if (isset($this->config->bibid)) { - $openURL .= '&pid=' . - 'bibid%3D' . $this->config->bibid; - } else { - // use IP-based request as fallback - $openURL .= '&pid=client_ip%3D' . $_SERVER['REMOTE_ADDR']; - } - $openURL .= !isset($parsed['rft.issn']) && isset($parsed['zdbid']) ? - '&zdbid=' . $parsed['zdbid'] : ''; - - $openURL .= urlencode('&ezb=1'); - $url = $this->getResolverUrl($openURL); // Make the call to the EZB and load results @@ -167,14 +135,84 @@ class Ezb extends AbstractBase implements TranslatorAwareInterface $this->getElectronicResults('2', 'Licensed', $records, $xpath); $this->getElectronicResults('3', 'Partially licensed', $records, $xpath); $this->getElectronicResults('4', 'Not free', $records, $xpath); + $this->getElectronicResults('10', 'Unknown Electronic', $records, $xpath); // get results for print, only if available $this->getPrintResults('2', 'Print available', $records, $xpath); $this->getPrintResults('3', 'Print partially available', $records, $xpath); + $this->getPrintResults('10', 'Unknown Print', $records, $xpath); return $records; } + /** + * Get Resolver Url + * + * Transform the OpenURL as needed to get a working link to the resolver. + * + * @param string $openURL openURL (url-encoded) + * + * @return string Link + */ + public function getResolverUrl($openURL) + { + // Unfortunately the EZB-API only allows OpenURL V0.1 and + // breaks when sending a non expected parameter (like an ISBN). + // So we do have to 'downgrade' the OpenURL-String from V1.0 to V0.1 + // and exclude all parameters that are not compliant with the EZB. + + // Parse OpenURL into associative array: + $tmp = explode('&', $openURL); + $parsed = []; + + foreach ($tmp as $current) { + $tmp2 = explode('=', $current, 2); + $parsed[$tmp2[0]] = $tmp2[1]; + } + + // Downgrade 1.0 to 0.1 + if ($parsed['ctx_ver'] == 'Z39.88-2004') { + $openURL = $this->downgradeOpenUrl($parsed); + } + + // BOF - finc specific PID-Zone parameter + + // use JOP supported identifier bibid (finc specific as VuFind does not + // support resolver specific configuration) + if (isset($this->config->bibid)) { + $pid = 'bibid=' . $this->config->bibid; + } else { + // use IP-based request as fallback + $pid = 'client_ip=' . $_SERVER['REMOTE_ADDR']; + } + + // use finc indexed zdbid if issn is not given + $pid .= !isset($parsed['rft.issn']) && isset($parsed['zdbid']) ? + '&zdbid=' . $parsed['zdbid'] : ''; + + // limit results to ezb only (set to true) + if (isset($this->config->ezb_only) && $this->config->ezb_only) { + $pid .= 'ezb=1'; + } + // limit results to zdb only (set to true) + if (isset($this->config->zdb_only) && $this->config->zdb_only) { + $pid .= 'zdb=1'; + } + + $openURL .= '&pid=' . urlencode($pid); + + // EOF - finc specific PID-Zone parameter + + // as of October 2020 the JOP resolver supports DOIs and we want to use + // DOIs for request in finc + $openURL .= isset($parsed['doi']) ? '&id=doi:' . $parsed['doi'] : ''; + + // Make the call to the EZB and load results + $url = $this->baseUrl . '?' . $openURL; + + return $url; + } + /** * Allows for resolver driver specific enabling/disabling of the more options * link which will link directly to the resolver URL. This should return false if @@ -188,45 +226,6 @@ class Ezb extends AbstractBase implements TranslatorAwareInterface // user return false; } - - /** - * Downgrade an OpenURL from v1.0 to v0.1 for compatibility with EZB. - * - * @param array $parsed Array of parameters parsed from the OpenURL. - * - * @return string EZB-compatible v0.1 OpenURL - */ - protected function downgradeOpenUrl($parsed) - { - $downgraded = []; - - $map = [ - 'rfr_id' => 'sid', - 'rft.date' => 'date', - 'rft.issn' => 'issn', - 'rft.volume' => 'volume', - 'rft.issue' => 'issue', - 'rft.spage' => 'spage', - 'rft.pages' => 'pages', - ]; - - // ignore all other parameters - foreach ($parsed as $key => $value) { - // exclude empty parameters - if (isset($value) && $value !== '') { - if (isset($map[$key])) { - $downgraded[] = "{$map[$key]}=$value"; - } elseif (in_array($key,$map)) { - $downgraded[] = "$key=$value"; - } - } - } - if (!empty($downgraded)) { - return "genre=article&".implode('&', $downgraded); - } - - return implode('&',$parsed); - } /** * Extract electronic results from the EZB response and inject them into the @@ -264,8 +263,8 @@ class Ezb extends AbstractBase implements TranslatorAwareInterface $state_access_mapping = [ '-1' => 'error', '0' => 'open', - '1' => 'limited', - '2' => 'open', + '1' => 'open', + '2' => 'limited', '3' => 'limited', '4' => 'denied', '5' => 'denied', @@ -275,13 +274,16 @@ class Ezb extends AbstractBase implements TranslatorAwareInterface $i = 0; foreach ($results as $result) { $record = []; + + // get title from XPath Element defined in $xpathTitleSelector $titleXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . - "Result[@state={$state}][".($i+1)."]/Title"; + "Result[@state={$state}][" . ($i + 1) . "]/" . static::$xpathTitleSelector; $title = $xpath->query($titleXP, $result)->item(0); if (isset($title)) { $record['title'] = strip_tags($title->nodeValue); } + // get additional coverage information $additionalXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . "Result[@state={$state}][".($i+1)."]/Additionals/Additional"; $additionalType = ['nali', 'intervall', 'moving_wall']; @@ -298,11 +300,21 @@ class Ezb extends AbstractBase implements TranslatorAwareInterface $record['access'] = $state_access_mapping[$state]; - $urlXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . + // try to find direct access URL + $accessUrlXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . "Result[@state={$state}][".($i+1)."]/AccessURL"; - $url = $xpath->query($urlXP, $result)->item(0); - if (isset($url->nodeValue)) { - $record['href'] = $url->nodeValue; + $accessUrl = $xpath->query($accessUrlXP, $result)->item(0); + + // try to find journal URL as fallback for direct access URL + $journalUrlXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . + "Result[@state={$state}][" . ($i + 1) . "]/JournalURL"; + $journalUrl = $xpath->query($journalUrlXP, $result)->item(0); + + // return direct access URL if available otherwise journal URL as fallback + if (isset($accessUrl->nodeValue)) { + $record['href'] = $accessUrl->nodeValue; + } elseif (isset($journalUrl)) { + $record['href'] = $journalUrl->nodeValue; } $readmeXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . @@ -366,19 +378,19 @@ class Ezb extends AbstractBase implements TranslatorAwareInterface $record['title'] = $coverage; $resultXP = "/OpenURLResponseXML/Full/PrintData/ResultList/" . - "Result[@state={$state}][".($i+1)."]"; + "Result[@state={$state}][" . ($i + 1) . "]"; $resultElements = [ 'Title', 'Location', 'Signature', 'Period', 'Holding_comment' ]; $elements = []; foreach ($resultElements as $element) { - $elem = $xpath->query($resultXP . "/".$element, $result)->item(0); + $elem = $xpath->query($resultXP . "/" . $element, $result)->item(0); if (isset($elem->nodeValue)) { $elements[$element] = strip_tags($elem->nodeValue); } } $record['coverage'] - = !empty($elements) ? implode(";", $elements) : $coverage; + = !empty($elements) ? implode("; ", $elements) : $coverage; $record['access'] = $state_access_mapping[$state]; diff --git a/module/finc/src/finc/Resolver/Driver/EzbTrait.php b/module/finc/src/finc/Resolver/Driver/EzbTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..8c74a4a2cec9c6b15d097556dfc093fe843a7515 --- /dev/null +++ b/module/finc/src/finc/Resolver/Driver/EzbTrait.php @@ -0,0 +1,254 @@ +<?php +/** + * EZB Link Resolver Driver Trait + * + * EZB is a free service -- the API endpoint is available at + * http://services.dnb.de/fize-service/gvr/full.xml + * + * API documentation is available at + * http://www.zeitschriftendatenbank.de/services/journals-online-print + * + * PHP version 7 + * + * Copyright (C) Markus Fischer, info@flyingfischer.ch + * + * last update: 2011-04-13 + * + * 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 Resolver_Drivers + * @author Markus Fischer <info@flyingfischer.ch> + * @author André Lahmann <lahmann@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:link_resolver_drivers Wiki + */ +namespace finc\Resolver\Driver; + +use DOMDocument; +use DOMXpath; + +/** + * EZB Link Resolver Driver + * + * @category VuFind + * @package Resolver_Drivers + * @author Markus Fischer <info@flyingfischer.ch> + * @author André Lahmann <lahmann@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:link_resolver_drivers Wiki + */ +trait EzbTrait +{ + /** + * As the JOP resolver provides also generic labels 'Article', 'Journal' + * etc. in element AccessLevel this label can be used as title for + * resolver results by setting this variable to 'AccessLevel' + * + * @var string + */ + protected static $xpathTitleSelector = 'Title'; + + /** + * Parse Links + * + * Parses an XML file returned by a link resolver + * and converts it to a standardised format for display + * + * @param string $xmlstr Raw XML returned by resolver + * + * @return array Array of values + */ + public function parseLinks($xmlstr) + { + $records = []; // array to return + + $xml = new DOMDocument(); + if (!@$xml->loadXML($xmlstr)) { + return $records; + } + + $xpath = new DOMXpath($xml); + + // get results for online + $this->getElectronicResults('0', 'Free', $records, $xpath); + $this->getElectronicResults('1', 'Partially free', $records, $xpath); + $this->getElectronicResults('2', 'Licensed', $records, $xpath); + $this->getElectronicResults('3', 'Partially licensed', $records, $xpath); + $this->getElectronicResults('4', 'Not free', $records, $xpath); + $this->getElectronicResults('10', 'Unknown Electronic', $records, $xpath); + + // get results for print, only if available + $this->getPrintResults('2', 'Print available', $records, $xpath); + $this->getPrintResults('3', 'Print partially available', $records, $xpath); + $this->getPrintResults('10', 'Unknown Print', $records, $xpath); + + return $records; + } + + /** + * Downgrade an OpenURL from v1.0 to v0.1 for compatibility with EZB. + * + * @param array $parsed Array of parameters parsed from the OpenURL. + * + * @return string EZB-compatible v0.1 OpenURL + */ + protected function downgradeOpenUrl($parsed) + { + $downgraded = []; + + // prepare content for downgrading + // resolver only accepts date formats YYYY, YYYY-MM, and YYYY-MM-DD + // in case we have a date in another format, drop the date information + if (isset($parsed['rft.date']) + && !preg_match('/^\d{4}(-\d\d(-\d\d)?)?$/', $parsed['rft.date']) + ) { + unset($parsed['rft.date']); + } + + $map = [ + 'rfr_id' => 'sid', + 'rft.date' => 'date', + 'rft.issn' => 'issn', + 'rft.isbn' => 'isbn', // isbn is supported as of 12/2021 + 'rft.volume' => 'volume', + 'rft.issue' => 'issue', + 'rft.spage' => 'spage', + 'rft.pages' => 'pages', + ]; + + // ignore all other parameters + foreach ($parsed as $key => $value) { + // exclude empty parameters + if (isset($value) && $value !== '') { + if (isset($map[$key])) { + $downgraded[] = "{$map[$key]}=$value"; + } elseif (in_array($key, $map)) { + $downgraded[] = "$key=$value"; + } + } + } + if (!empty($downgraded)) { + // we need 'genre' but only the values + // article or journal are allowed... + return "genre=article&" . implode('&', $downgraded); + } + + return implode('&', $parsed); + } + + /** + * Extract electronic results from the EZB response and inject them into the + * $records array. + * + * @param string $state The state attribute value to extract + * @param string $coverage The coverage string to associate with the state + * @param array $records The array of results to update + * @param DOMXpath $xpath The XPath object containing parsed XML + * + * @return void + */ + protected function getElectronicResults($state, $coverage, &$records, $xpath) + { + $results = $xpath->query( + "/OpenURLResponseXML/Full/ElectronicData/ResultList/Result[@state=" . + $state . "]" + ); + + /* + * possible state values: + * -1 ISSN nicht eindeutig + * 0 Standort-unabhängig frei zugänglich + * 1 Standort-unabhängig teilweise zugänglich (Unschärfe bedingt durch + * unspezifische Anfrage oder Moving-Wall) + * 2 Lizenziert + * 3 Für gegebene Bibliothek teilweise lizenziert (Unschärfe bedingt durch + * unspezifische Anfrage oder Moving-Wall) + * 4 nicht lizenziert + * 5 Zeitschrift gefunden + * Angaben über Erscheinungsjahr, Datum ... liegen außerhalb des + * hinterlegten bibliothekarischen Zeitraums + * 10 Unbekannt (ISSN unbekannt, Bibliothek unbekannt) + */ + $state_access_mapping = [ + '-1' => 'error', + '0' => 'open', + '1' => 'open', + '2' => 'limited', + '3' => 'limited', + '4' => 'denied', + '5' => 'denied', + '10' => 'unknown' + ]; + + $i = 0; + foreach ($results as $result) { + $record = []; + + // get title from XPath Element defined in $xpathTitleSelector + $titleXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . + "Result[@state={$state}][" . ($i + 1) . "]/" . + static::$xpathTitleSelector; + $title = $xpath->query($titleXP, $result)->item(0); + if (isset($title)) { + $record['title'] = strip_tags($title->nodeValue); + } + + // get additional coverage information + $additionalXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . + "Result[@state={$state}][" . ($i + 1) . "]/Additionals/Additional"; + $additionalType = ['nali', 'intervall', 'moving_wall']; + $additionals = []; + foreach ($additionalType as $type) { + $additional = $xpath + ->query($additionalXP . "[@type='" . $type . "']", $result) + ->item(0); + if (isset($additional->nodeValue)) { + $additionals[$type] = strip_tags($additional->nodeValue); + } + } + $record['coverage'] + = !empty($additionals) ? implode("; ", $additionals) : $coverage; + + $record['access'] = $state_access_mapping[$state]; + + // try to find direct access URL + $accessUrlXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . + "Result[@state={$state}][" . ($i + 1) . "]/AccessURL"; + $accessUrl = $xpath->query($accessUrlXP, $result)->item(0); + + // try to find journal URL as fallback for direct access URL + $journalUrlXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" . + "Result[@state={$state}][" . ($i + 1) . "]/JournalURL"; + $journalUrl = $xpath->query($journalUrlXP, $result)->item(0); + + // return direct access URL if available otherwise journal URL fallback + if (isset($accessUrl->nodeValue)) { + $record['href'] = $accessUrl->nodeValue; + } elseif (isset($journalUrl)) { + $record['href'] = $journalUrl->nodeValue; + } + // Service type needs to be hard-coded for calling code to properly + // categorize links. The commented code below picks a more appropriate + // value but won't work for now -- retained for future reference. + //$service_typeXP = "/OpenURLResponseXML/Full/ElectronicData/ResultList/" + // . "Result[@state={$state}][".($i+1)."]/AccessLevel"; + //$record['service_type'] + // = $xpath->query($service_typeXP, $result)->item(0)->nodeValue; + $record['service_type'] = 'getFullTxt'; + array_push($records, $record); + $i++; + } + } +} diff --git a/module/finc/src/finc/Resolver/Driver/Redi.php b/module/finc/src/finc/Resolver/Driver/Redi.php index 6bd0a2d30d8abfa2050edf0e13467ce9d0bac8a5..c95c4a029c8fb49edbae40e87692739d5817feaf 100644 --- a/module/finc/src/finc/Resolver/Driver/Redi.php +++ b/module/finc/src/finc/Resolver/Driver/Redi.php @@ -15,7 +15,7 @@ */ namespace finc\Resolver\Driver; -use \VuFind\Resolver\Driver\Redi as RediBase; +use VuFind\Resolver\Driver\Redi as RediBase; /** * Redi Link Resolver Driver @@ -29,7 +29,6 @@ use \VuFind\Resolver\Driver\Redi as RediBase; */ class Redi extends RediBase { - use ResolverTrait; /** * Constructor diff --git a/module/finc/src/finc/Resolver/Driver/ResolverTrait.php b/module/finc/src/finc/Resolver/Driver/ResolverTrait.php deleted file mode 100644 index 29ad59d61ef1e3545d8f9faf45eb78a84f6cdfed..0000000000000000000000000000000000000000 --- a/module/finc/src/finc/Resolver/Driver/ResolverTrait.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php -/** - * Finc Resolver Trait - * - * PHP version 5 - * - * Copyright (C) Leipzig University Library 2015 - * - * 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. - * - * @category VuFind - * @package Resolver_Drivers - * @author Robert Lange <lange@ub.uni-leipzig.de> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:plugins:link_resolver_drivers Wiki - */ -namespace finc\Resolver\Driver; - -use \Zend\Config\Exception\InvalidArgumentException; - -/** - * Finc Link Resolver Driver - * - * @category VuFind - * @package Resolver_Drivers - * @author Robert Lange <lange@ub.uni-leipzig.de> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:plugins:link_resolver_drivers Wiki - */ -trait ResolverTrait -{ - /** - * Get Resolver Url - * - * Transform the OpenURL as needed to get a working link to the resolver. - * - * @param string $openURL openURL (url-encoded) - * - * @return string Returns resolver specific url - */ - public function getResolverUrl($openURL) - { - if (empty($this->baseUrl)) { - throw new InvalidArgumentException( - 'Base url for link resolver is not correctly configured.' - ); - } - - if (empty($openURL)) { - return $this->baseUrl; - } - - if ($openURL[0] === '?' || $openURL[0] === '&') { - $openURL = substr($openURL, 1, strlen($openURL)-1); - } - - if (strpos($this->baseUrl, '?') === false) { - return $this->baseUrl . '?' . $openURL; - } else { - return $this->baseUrl . '&' . $openURL; - } - } -} diff --git a/module/finc/src/finc/View/Helper/Root/ExternalLink.php b/module/finc/src/finc/View/Helper/Root/ExternalLink.php index 91b1df9feaadbd1b7b2d51291c99fbd5a88d46d9..6bfbb6de3ccd846cfaef56c46476b148458683ec 100644 --- a/module/finc/src/finc/View/Helper/Root/ExternalLink.php +++ b/module/finc/src/finc/View/Helper/Root/ExternalLink.php @@ -27,6 +27,9 @@ */ namespace finc\View\Helper\Root; +use Zend\View\Helper\EscapeHtml; +use Zend\View\Helper\EscapeHtmlAttr; + /** * External link view helper * @@ -38,13 +41,6 @@ namespace finc\View\Helper\Root; */ class ExternalLink extends \Zend\View\Helper\AbstractHelper { - /** - * Context view helper - * - * @var \VuFind\View\Helper\Root\Translate - */ - protected $translator; - /** * Default html attributes for external links * @@ -55,6 +51,13 @@ class ExternalLink extends \Zend\View\Helper\AbstractHelper 'rel' => 'noopener' ]; + /** + * Escape manuelly flag + * + * @var bool + */ + protected $manualEscape; + /** * Renders an anchor element (hyperlink) to an external website * @@ -62,7 +65,8 @@ class ExternalLink extends \Zend\View\Helper\AbstractHelper * @param string $label desired label or translation token * @param array $attributes more attributes to apply to the a element, * key-value-pairs - * @param bool $translateLabel true if label shall be translated + * @param bool $manualEscape true if $label conatins html to display and + * $attributes are escped before calling method * * @return string */ @@ -70,36 +74,55 @@ class ExternalLink extends \Zend\View\Helper\AbstractHelper $href, $label = '', $attributes = [], - $translateLabel = false + $manualEscape = false ) { + $this->manualEscape = $manualEscape; + $link = '<a href="' . $href . '"'; $attributes = array_merge($this->defaultAttributes, $attributes); foreach ($attributes as $key => $value) { - $link .= ' ' . $key . '="' . $value . '"'; + $link .= ' ' . $key . '="' . $this->escapeHtmlAttr($value) . '"'; } $link .= '>'; if (empty($label)) { $label = $href; - } elseif ($translateLabel) { - $label = $this->translate($label); } - $link .= $label . '</a>'; + $link .= $this->escapeHtml($label) . '</a>'; return $link; } /** - * Helper function to provide access to Translate ViewHelper + * Helper function to provide access to EscapeHTML ViewHelper + * and esacpe lable if not disabled + * + * @param string $html html to escape + * + * @return string + */ + protected function escapeHtml($html) + { + if ($this->manualEscape) { + return $html; + } + $htmlEscaper = $this->getView()->plugin('escapeHtml'); + return $htmlEscaper($html); + } + + /** + * Helper function to provide access to EscaperHTML Attribute ViewHelper + * and esacpe HTML attributes if not disabled * * @param string $label translation token * * @return string */ - protected function translate($label) + protected function escapeHtmlAttr($htmlAttr) { - if (!isset($this->translator)) { - $this->translator = $this->getView()->plugin('translate'); + if ($this->manualEscape) { + return $htmlAttr; } - return $this->translator->translate($label); + $htmlAttrEscaper = $this->getView()->plugin('escapeHtmlAttr'); + return $htmlAttrEscaper($htmlAttr); } } diff --git a/module/finc/src/finc/View/Helper/Root/Factory.php b/module/finc/src/finc/View/Helper/Root/Factory.php index 4e44d5bae6a6e2bf0f8fa2a1aac7ae8a438cfb84..cefff99a0a9776e86830d915d1fa23799e1fc473 100644 --- a/module/finc/src/finc/View/Helper/Root/Factory.php +++ b/module/finc/src/finc/View/Helper/Root/Factory.php @@ -72,7 +72,8 @@ class Factory $container->get('ViewHelperManager')->get('url'), $container->get('VuFind\AuthManager'), $container->get(EblRewrite::class), - $container->get('VuFind\Config')->get('Resolver') + $container->get('VuFind\Config')->get('Resolver'), + $container->get('VuFind\Config')->get('iconMapping') ); //due to https://github.com/vufind-org/vufind/pull/718 @@ -211,19 +212,6 @@ class Factory ); } - /** - * Construct Head Title Helper - * - * @param ContainerInterface $container Service manager - * - * @return HeadTitle - */ - public static function getHeadTitle(ContainerInterface $container) - { - $config = $container->get('VuFind\Config')->get('config')->Site; - return new HeadTitle($config->title ?? ''); - } - /** * Construct ExternalLink * diff --git a/module/finc/src/finc/View/Helper/Root/Record.php b/module/finc/src/finc/View/Helper/Root/Record.php index 63545be5c92d7a5ea7cad1748e9a83bc48de1760..cd77fd4975901d436d3f9209ec5411b25a25613f 100644 --- a/module/finc/src/finc/View/Helper/Root/Record.php +++ b/module/finc/src/finc/View/Helper/Root/Record.php @@ -28,7 +28,6 @@ namespace finc\View\Helper\Root; use finc\Rewrite\EblRewrite; -use Symfony\Component\Config\Definition\Exception\Exception; /** * Record driver view helper @@ -77,75 +76,119 @@ class Record extends \VuFind\View\Helper\Root\Record protected $resolverConfig; /** - * Constructor + * Mapping for facets on style based icons + * + * @var \Zend\Config\Config + */ + protected $iconConfig; + + /** + * Type of Mapping for record on its icon + * + * @var string + */ + protected $iconMappingType; + + /** + * Mapping for style based record icons + * + * @var array + */ + protected $iconMappings = []; + + /** + * Handling for array of entry keys * - * @param \Zend\Config\Config $config VuFind configuration - * @param \Zend\View\Helper\Url $helper URL helper - * @param \VuFind\Auth\Manager $manager AuthManager - * @param mixed $rewrite - * @param mixed $resolverConfig + * True => concatenate keys for mapping + * False => use first key only for mapping + * + * @var string + */ + protected $concatKeysForIconMapping; + + /** + * Constructor * + * @param \Zend\Config\Config $config VuFind configuration + * @param \Zend\View\Helper\Url $helper URL helper + * @param \VuFind\Auth\Manager $manager AuthManager + * @param mixed $rewrite Ebl rewrite service for VuFind + * @param mixed $resolverConfig Resolver.ini + * @param mixed $iconConfig iconMapping.ini */ public function __construct( $config, \Zend\View\Helper\Url $helper, \VuFind\Auth\Manager $manager, $rewrite, - $resolverConfig + $resolverConfig, + $iconConfig = null ) { parent::__construct($config); $this->url = $helper; $this->manager = $manager; $this->rewrite = $rewrite; $this->resolverConfig = $resolverConfig; + + if ($iconConfig) { + $this->iconConfig = $iconConfig; + $this->iconMappingType = $this->iconConfig + ->StyleBasedIcons->type ?? 'None'; + $this->iconMappings = isset( + $this->iconConfig + ->StyleBasedIcons->{$this->iconMappingType} + ) ? $this->iconConfig + ->StyleBasedIcons->{$this->iconMappingType}->toArray() : []; + $this->concatKeysForIconMapping = $this->iconConfig + ->StyleBasedIcons->concatenateKeys ? true : false; + } } /** * Render a (list of) record icons. * - * @param string $tpl Define alternative template for record icon. Default: record-icon.phtml - * @param bool $addText Return additional access / format. Default: true + * @param string $tpl Template for record icon, default: record-icon.phtml + * @param bool $addText Return additional access / format. Default: true * * @return string */ - public function getRecordIcon(string $tpl = 'record-icon', bool $addText = true): string - { + public function getRecordIcon( + string $tpl = 'record-icon', + bool $addText = true + ): string { $iconType = $this->getRecordIconType(); $iconClass = $this->getRecordIconClass($iconType); return $this->renderTemplate($tpl . '.phtml', ['iconClass' => $iconClass]) - . ($addText ? $this->getRecordIconText($iconType,$iconClass) : ''); + . ($addText ? $this->getRecordIconText($iconType, $iconClass) : ''); } /** * Get the CSS class used to properly render an icon for given value * - * @param string $value (identifier) Value to convert into CSS class. Default: null - * @param string $classfile Define alternative file for icon class without - * suffix. Default: record-icon-class.phtml + * @param string $key (identifier) Value to convert into CSS class. Default: null * * @return string */ - public function getRecordIconClass(string $value = null, string $classfile = 'record-icon-class'): string + public function getRecordIconClass(string $key = "default"): string { - if ($value === null) { - $value = $this->getRecordIconType(); - } - - return $this->renderTemplate( - $classfile . '.phtml', - ['value' => $value] - ); + return $this->iconMappings[$key] ?? + $this->iconMappings["default"] ?? "unknown"; } /** * Get the CSS class used to properly render an icon for given value * - * @param string $classfile Define alternative file for icon text without + * @param string|null $iconType icon type + * @param string|null $iconClass css class for icon + * @param string $classfile Define alternative file for icon text without * * @return string */ - public function getRecordIconText(string $iconType = null, string $iconClass = null, $classfile = 'record-icon-text'): string - { + public function getRecordIconText( + string $iconType = null, + string $iconClass = null, + string $classfile = 'record-icon-text' + ): string { if (null === $iconType) { $iconType = $this->getRecordIconType(); } @@ -153,22 +196,36 @@ class Record extends \VuFind\View\Helper\Root\Record $iconClass = $this->getRecordIconClass($iconType); } return $this->renderTemplate( - $classfile . '.phtml', compact('iconType','iconClass') + $classfile . '.phtml', + compact('iconType', 'iconClass') ); } - function getRecordIconType() { - - /* block copied from record-icon - RL */ + /** + * Get icon by record type + * + * @return string + */ + protected function getRecordIconType() + { $recordType = $this->driver->getRecordType(); - switch ($recordType) { - case 'lido': - return "object"; - case 'missing': - case 'marcfincpda': - return $recordType; - default: - return implode('', $this->driver->getFacetAvail()); + switch ($recordType) + { + case 'lido': + return "object"; + case 'missing': + case 'marcfincpda': + return $recordType; + default: + $types = explode('_', $this->iconMappingType); + $recordType = ''; + foreach ($types as $type) { + $recordTypes = $this->driver->tryMethod("get{$type}") ?? ['default']; + $recordTypes = $this->concatKeysForIconMapping ? + implode('', $recordTypes) : $recordTypes[0] ?? 'default'; + $recordType .= strtolower(str_replace([' ','-',','], '', $recordTypes)); + } + return $recordType; } } @@ -179,76 +236,7 @@ class Record extends \VuFind\View\Helper\Root\Record */ public function showStyleBasedIcons() { - return isset($this->config->Content->showStyleBasedIcons) ? - $this->config->Content->showStyleBasedIcons : false; - } - - /** - * Get external access links to other ILS defined by config setting. - * - * @return array Associative array. - * @throws Exception Value of source ids has to be numeric. - * @deprecated Deprecated due to View Helper ExternalCatalogueLink->getLinks() - * @see https://projekte.ub.uni-leipzig.de/issues/10543 - */ - public function getExternalAccessLinks() - { - $i = -1; // iterator of extUrls - $extUrls = []; - - // if configuration empty return unprocessed - if (!isset($this->config->ExternalAccess) - || count($this->config->ExternalAccess) == 0 - ) { - return []; - } - // if institutions empty return unprocessed - $institutions = $this->driver->tryMethod('getInstitutions'); - if (!isset($institutions) || count($institutions) == 0) { - return []; - } - foreach ($this->config->ExternalAccess as $recordType => $accessUrl) { - $replaceId = null; - // get identifier of record id type - switch ($recordType) { - case "id": - $replaceId = $this->driver->getUniqueID(); - break; - case "ppn": - $replaceId = $this->driver->tryMethod('getRID'); - break; - default: - $replaceId = null; - } - foreach ($accessUrl as $institution => $urlPattern) { - // source_id filter - if (is_array($urlPattern)) { - foreach ($urlPattern as $source_id => $pattern) { - $urlPattern = $pattern; - $sids = explode(',', $source_id); - if (!array_product(array_map('is_numeric', $sids))) { - throw new Exception( - 'Value of source ids has to be numeric.' - ); - } - $sourceID = $this->driver->tryMethod('getSourceID'); - $replaceId = (isset($sourceID) - && true === in_array($sourceID, $sids)) - ? $replaceId : null; - break; - } - } - - // institution filter - if (true === in_array($institution, $institutions) - && !empty($replaceId) - ) { - $extUrls[++$i]['desc'] = $institution; - $extUrls[$i]['url'] = sprintf($urlPattern, $replaceId); - } - } - } - return $extUrls; + return $this->config->Content->showStyleBasedIcons ?? false; } /** @@ -265,7 +253,9 @@ class Record extends \VuFind\View\Helper\Root\Record $type == 'author' ? $this->removeAuthorDates($lookfor) : $lookfor ); - return parent::getLink($type, $lookfor); + /* remove invalid new line in link templates, + remove after fixed in templates or VuFind parent */ + return str_replace(["\n"], '', parent::getLink($type, $lookfor) ?? ''); } /** @@ -313,9 +303,9 @@ class Record extends \VuFind\View\Helper\Root\Record public function removeAuthorDates($author) { $match = []; - $regex = - '/^(\s|.*)\s(fl.\s|d.\s|ca.\s|\*)*\s?(\d{4})\??(\sor\s\d\d?)?\s?' - . '(-|–)?\s?(ca.\s|after\s|†)?(\d{1,4})?(.|,)?$/Uu'; + $regex + = '/^(\s|.*)\s(fl.\s|d.\s|ca.\s|\*)*\s?(\d{4})\??(\sor\s\d\d?)?\s?' + . '(-|–)?\s?(ca.\s|after\s|†)?(\d{1,4})?(.|,)?$/Uu'; if (preg_match($regex, $author, $match)) { $author = (isset($match[1])) ? trim($match[1]) : $author; } @@ -373,13 +363,16 @@ class Record extends \VuFind\View\Helper\Root\Record if (0 != preg_match( '/' . addcslashes($r['remove'], '/') . '/i', trim($link['url']) - )) { + ) + ) { unset($link); return; } } // is pattern set and matches so try rewrite url - if (isset($r['pattern']) && 0 != preg_match('/' . $r['pattern'] . '/i', trim($link['url']))) { + if (isset($r['pattern']) + && 0 != preg_match('/' . $r['pattern'] . '/i', trim($link['url'])) + ) { // if function is set and available then perform on url if (isset($r['function']) && is_callable($r['function'])) { $link['url'] = $r['function']($link['url']); @@ -517,18 +510,10 @@ class Record extends \VuFind\View\Helper\Root\Record $onlineEditions = []; $recordLinkHelper = $this->getView()->plugin('recordLink'); foreach ($this->driver->tryMethod('getAdditionals') as $add) { - if ( - isset($add['identifier']) - && - ( - in_array($add['identifier'], $online_keys) - || - ( - isset($add['note']) - && - in_array($add['note'], $online_keys) - ) - ) + if (isset($add['identifier']) + && (in_array($add['identifier'], $online_keys) + || (isset($add['note']) + && in_array($add['note'], $online_keys))) ) { if (isset($add['id'])) { $link = $recordLinkHelper->getTabUrl($add['id'], 'Holdings'); @@ -551,6 +536,11 @@ class Record extends \VuFind\View\Helper\Root\Record return $onlineEditions; } + /** + * Get advanced query params + * + * @return array + */ public function getAdvancedSearchQueryParams() { $queryParams = []; @@ -564,7 +554,8 @@ class Record extends \VuFind\View\Helper\Root\Record $queryParams['type0'][] = 'ISN'; } if ($author = $this->driver->tryMethod('getPrimaryAuthors')) { - $queryParams['lookfor0'][] = is_array($author) ? current($author) : $author; + $queryParams['lookfor0'][] = is_array($author) ? + current($author) : $author; $queryParams['type0'][] = 'Author'; } if (!empty($queryParams)) { @@ -578,11 +569,12 @@ class Record extends \VuFind\View\Helper\Root\Record /** * Get the rendered cover plus some useful parameters. * - * @deprecated used for VF7 ajaxcovers, remove on upgrade * @param string $context Context of code being generated * @param string $default The default size of the cover * @param string $link The link for the anchor * + * @deprecated used for VF7 ajaxcovers, remove on upgrade + * * @return array */ public function getCoverDetails($context, $default, $link = false) @@ -604,11 +596,12 @@ class Record extends \VuFind\View\Helper\Root\Record } if ($details['size'] === false) { // we need to set a size for the ajax covers to work correctly - list($details['size']) = explode(':', $preferredSize); + [$details['size']] = explode(':', $preferredSize); } $details['html'] = $this->contextHelper->renderInContext( - 'record/cover.phtml', $details + 'record/cover.phtml', + $details ); } return $details; @@ -617,10 +610,11 @@ class Record extends \VuFind\View\Helper\Root\Record /** * Generate a thumbnail URL (return false if unsupported). * - * @deprecated used for VF7 ajaxcovers, remove on upgrade * @param string $size Size of thumbnail (small, medium or large -- small is * default). * + * @deprecated used for VF7 ajaxcovers, remove on upgrade + * * @return string|bool */ public function getThumbnail($size = 'small') diff --git a/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php b/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php index 372032924f2c49ace68fa61226e0660a22dbcf41..4b8ae4866d25c59f90f315af5305f28e94578535 100644 --- a/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php +++ b/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php @@ -71,6 +71,10 @@ class RecordDataFormatterFactory 'description', [$this, 'getDefaultDescriptionSpecs'] ); + $helper->setDefaults( + 'toc', + [$this, 'getTOCSpecs'] + ); $helper->setDefaults( 'core-ai', [$this, 'getAiCoreSpecs'] @@ -893,4 +897,20 @@ class RecordDataFormatterFactory ? 'rda_original_title' : 'non_rda_original_title'; } + + /** + * Get marc specifications for displaying table of contents. + * + * @return array + */ + public function getTOCSpecs() + { + $spec = new RecordDataFormatter\SpecBuilder(); + $spec->setTemplateLine( + 'Table of Contents', + 'getTOC', + 'data-toc.phtml' + ); + return $spec->getArray(); + } } diff --git a/module/finc/tests/fixtures/externallink/testexternallink1.json b/module/finc/tests/fixtures/externallink/testexternallink1.json index a003aa3234013507e659fe5dd78f4acec55064df..d54c4d9d20cb2076bf46475c9af1f6bc1aab536d 100644 --- a/module/finc/tests/fixtures/externallink/testexternallink1.json +++ b/module/finc/tests/fixtures/externallink/testexternallink1.json @@ -1,5 +1,5 @@ { - "ppn": { + "record_id": { "DE-540": [{ "pattern": "http://194.94.197.6/libero/WebopacOpenURL.cls?ACTION=DISPLAY&LANG=DE&RID=%s" }], diff --git a/module/finc/tests/fixtures/getUrls/IIIF_0-1763648745_fullrecord.txt b/module/finc/tests/fixtures/getUrls/IIIF_0-1763648745_fullrecord.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f026ebd72b67266d4fe46891bc1e3b4c9c8e1ef --- /dev/null +++ b/module/finc/tests/fixtures/getUrls/IIIF_0-1763648745_fullrecord.txt @@ -0,0 +1 @@ +03910nam a22009732 45000010013000000030007000130050017000200070015000370080041000520240039000930350023001320350026001550350022001810400031002030410008002340840043002421000030002852450247003152460014005622640009005763000037005853360026006223370032006483380037006805000057007175020058007745060106008325330104009385350015010425400083010575400104011405830068012446550091013127510074014037760214014778500007016918560114016988560119018129120016019319360192019479510007021398520032021468560065021788520031022439700007022749710010022819720010022919730007023019350009023088520016023178520018023338520017023518520017023688520018023858520016024038520018024198520018024378520018024558520017024738520017024908520018025078520017025258560065025428520031026079700007026389710010026459720010026559730007026659350009026728520016026818520018026978520017027158520017027328520018027498520016027678520018027838520018028018520018028198520017028378520017028548520018028718520017028899800030029060-1763648745DE-62720210719201444.0cr uuu---uuuuu210719s1965 xx |||||om 00| ||ger c7 aurn:nbn:de:bsz:15-0020-3325332urn a(DE-627)1763648745 a(DE-599)KXP1763648745 a(OCoLC)1260628169 aDE-627bgercDE-627erakwb ager aZX 70002rvk0(DE-625)rvk/158439:142571 aGöldner, Karl-Heinz4aut10aZum gegenseitigen Führen, Ein- und Unterordnen der Schüler im Sportunterrichtbein Beitrag zur Erziehung der Schüler zur Kollektivität im Sportunterricht; dargestellt auf der Grundlage eines pädagogischen ExperimentscKarl-Heinz Göldner30aEinordnen 1c1965 aIX, 200, XXVI Bl.bgraph. Darst. aTextbtxt2rdacontent aComputermedienbc2rdamedia aOnline-Ressourcebcr2rdacarrier aWahrnehmung der Rechte durch die VG Wort (§ 51 VGG) aLeipzig, Dt. Hochsch. für Körperkultur, Diss., 19650 qDE-15aOpen AccesseControlled Vocabulary for Access Rightsuhttp://purl.org/coar/access_right/c_abf2 aOnline-AusgabebLeipzigcUniversitätsbibliothek Leipzigd2020e1 Online-Ressource7|2020||||||||||1 aUB Leipzig qDE-15aUrheberrechtsschutz 1.02rsuhttp://rightsstatements.org/vocab/InC/1.0/ qDE-15aFreier Zugang - Rechte vorbehalten 1.0fUBL FZ-RV 1.0uhttps://www.ub.uni-leipzig.de/fz-rv-11 aArchivierung/Langzeitarchivierung gewährleistet2pdager5DE-15 7aHochschulschrift0(DE-588)4113937-90(DE-627)1058257780(DE-576)2094805802gnd-content aLeipzig0(DE-588)4035206-70(DE-627)1047989980(DE-576)2090112464uvp08iElektronische Reproduktion vonaGöldner, Karl-HeinztZum gegenseitigen Führen, Ein- und Unterordnen der Schüler im Sportunterrichtd1965hIX, 200, XXVI Bl.w(DE-627)142511671Xw(DE-576)355116715nUB Leipzig aaa40uhttp://nbn-resolving.org/urn:nbn:de:bsz:15-0020-332533xDigitalisierungyOnline-Zugriffzkostenfrei3Volltext42uhttps://iiif.ub.uni-leipzig.de/0000030933/manifest.jsonmB:DE-15qapplication/jsonxDigitalisierungyIIIF-Manifest aZDB-175-LHSrvaZX 7000bAllgemeines; EinführungenkSportkSportwissenschaftkTheorie des SportskAllgemeineskAllgemeines; Einführungen0(DE-627)139653542X0(DE-625)rvk/158439:142570(DE-576)32653542X aBO aDE-15z2021-07-19T00:00:00Z40uhttp://nbn-resolving.org/urn:nbn:de:bsz:15-0020-3325339LFER aLFERz2021-08-10T19:50:52Z cOD cEBOOK cEBOOK cEB alfer 2lferaDE-15 2lferaDE-Brt1 2lferaDE-Rs1 2lferaDE-Ch1 2lferaDE-L229 2lferaDE-14 2lferaDE-Pl11 2lferaDE-Gla1 2lferaDE-Zwi2 2lferaDE-Zi4 2lferaDE-Bn3 2lferaDE-D161 2lferaDE-10540uhttp://nbn-resolving.org/urn:nbn:de:bsz:15-0020-3325339LFER aLFERz2021-09-14T20:01:17Z cOD cEBOOK cEBOOK cEB alfer 2lferaDE-15 2lferaDE-Brt1 2lferaDE-Rs1 2lferaDE-Ch1 2lferaDE-L229 2lferaDE-14 2lferaDE-Pl11 2lferaDE-Gla1 2lferaDE-Zwi2 2lferaDE-Zi4 2lferaDE-Bn3 2lferaDE-D161 2lferaDE-105 a1763648745b0k1763648745 \ No newline at end of file diff --git a/module/finc/tests/fixtures/getUrls/IIIF_0-1763648745_result.json b/module/finc/tests/fixtures/getUrls/IIIF_0-1763648745_result.json new file mode 100644 index 0000000000000000000000000000000000000000..9d36b72bea9d58cd7b95cc97aa6aa4c4d07d15bf --- /dev/null +++ b/module/finc/tests/fixtures/getUrls/IIIF_0-1763648745_result.json @@ -0,0 +1,12 @@ +[ + { + "url": "http://nbn-resolving.org/urn:nbn:de:bsz:15-0020-332533", + "desc": "Full Text", + "indicators": "40" + }, + { + "url": "https://iiif.ub.uni-leipzig.de/0000030933/manifest.json", + "desc": "IIIF-Manifest, Digitalisierung", + "indicators": "42" + } +] \ No newline at end of file diff --git a/module/finc/tests/fixtures/getUrls/isil_with_DE-15_0-1497294835_fullrecord.txt b/module/finc/tests/fixtures/getUrls/isil_with_DE-15_0-1497294835_fullrecord.txt new file mode 100644 index 0000000000000000000000000000000000000000..5275de52855b249f62aba0d672f13f65736ac6fc --- /dev/null +++ b/module/finc/tests/fixtures/getUrls/isil_with_DE-15_0-1497294835_fullrecord.txt @@ -0,0 +1 @@ +03104naa a2200997 45000010013000000030007000130050017000200070015000370080041000520240041000930350023001340350022001570350025001790400031002040410008002350820008002431000091002512450062003422640051004043000021004553360026004763370032005023380037005345910065005716500071006366500076007076500074007836510070008579510007009278560083009348520032010179500009010499500014010589500017010729500010010899500015010999500028011149500018011429500011011609500011011719500015011829500009011979500010012069500015012169500014012319500014012459500025012599500029012849500035013139500016013489500012013649500018013769500015013949500015014099500023014249500030014479500009014779500039014869500031015259500009015569500042015659500048016079500041016559500010016969500040017069500009017469500011017559500027017669500007017939500015018009500025018159500026018409500022018669500020018889500019019089500014019279500013019419500015019549500017019699500017019869500015020039510010020189510010020288520027020389800041020650-1497294835DE-62720150306122414.0cr uuu---uuuuu150306s2015 xx |||||o 00| ||eng c7 aurn:nbn:de:bsz:15-qucosa-1619432urn a(DE-627)1497294835 a(DE-576)427294835 a(DE-599)BSZ427294835 aDE-627bgercDE-627erakwb aeng0 a7911 aStoppe, Sebastiand1978-0(DE-588)13178076X0(DE-627)6633567330(DE-576)34640150X4aut10aHow predictable are the Academy Awards?cSebastian Stoppe 1aLeipzigbUniversitätsbibliothek Leipzigc2015 aOnline-Ressource aTextbtxt2rdacontent aComputermedienbc2rdamedia aOnline-Ressourcebcr2rdacarrier aworkingPaper ; IMD-Felder und 1131 maschinell ergänzt (SWB)070(DE-588)4017102-40(DE-627)1045596830(DE-576)208918531aFilm2gnd070(DE-588)4204337-20(DE-627)10514410X0(DE-576)210161345aFilmpreis2gnd070(DE-588)4005227-80(DE-627)1047680290(DE-576)208859845aUmfrage2gnd 70(DE-588)4078704-70(DE-627)1060766120(DE-576)209209682aUSA2gnd aAR4 uhttp://nbn-resolving.de/urn:nbn:de:bsz:15-qucosa-161943yOnline-Zugriff9DE-15 aDE-15z2015-03-06T12:24:14Z aKino aSpielfilm aFilmaufnahme aFilme aSpielfilme aAudiovisuelles Material aVideokassette a电影 a電影 aФильм aFilm aPreis aFilmpreise a电影奬 a電影奬 aÐšÐ¸Ð½Ð¾Ð¿Ñ€ÐµÐ¼Ð¸Ñ aUnited States of America aVereinigte Staaten von Amerika aNordamerika aAmerika aUnited States aEtats Unis aEtats-Unis aVereinigte Staaten aEstados Unidos de America aEEUU aVereinigte Staaten von Nordamerika aSoedinennye Å taty Ameriki aSÅ A aStany Zjednoczone Ameryki Północnej aHÄ“nÅmenai Politeiai tÄ“s Boreiu AmerikÄ“s aHÄ“nÅmenes Politeies tÄ“s AmerikÄ“s aHÄ“PA aÄ’nÅmenes Politeies tÄ“s AmerikÄ“s aÄ’PA aMeiguo aEtats-Unis d'Amérique aUS aAmerikaner aBevölkerungsumfrage aRepräsentativumfrage aMeinungsbefragung aMeinungsumfrage aVolksbefragung aBefragung aUmfragen aDemoskopie aæ°‘æ„调查 aæ°‘æ„調查 aÐžÐ¿Ñ€Ð¾Ñ aXD-US bXA-DE 2fincaFID-MEDIEN-DE-15 a1497294835b0k1497294835o427294835 \ No newline at end of file diff --git a/module/finc/tests/fixtures/getUrls/isil_with_DE-15_0-1497294835_result.json b/module/finc/tests/fixtures/getUrls/isil_with_DE-15_0-1497294835_result.json new file mode 100644 index 0000000000000000000000000000000000000000..199d5009e410306fff651d44e7944e83341086b1 --- /dev/null +++ b/module/finc/tests/fixtures/getUrls/isil_with_DE-15_0-1497294835_result.json @@ -0,0 +1,7 @@ +[ + { + "url": "http://nbn-resolving.de/urn:nbn:de:bsz:15-qucosa-161943", + "desc": "Online-Zugriff", + "indicators": "4 " + } +] \ No newline at end of file diff --git a/module/finc/tests/fixtures/getUrls/no_isil_for_22-15-qucosa-161943_fullrecord.txt b/module/finc/tests/fixtures/getUrls/no_isil_for_22-15-qucosa-161943_fullrecord.txt new file mode 100644 index 0000000000000000000000000000000000000000..79c2b4b3541ca68917fa5a65e77a41e1dabec97c --- /dev/null +++ b/module/finc/tests/fixtures/getUrls/no_isil_for_22-15-qucosa-161943_fullrecord.txt @@ -0,0 +1 @@ +01725cam a2200397157450000100200000000700150002000800390003504100080007403700360008210000220011824500440014026000790018452005410026350000170080485600780082165000340089965000150093365000220094865000210097065000090099165000350100065000110103565000140104665000190106065000260107965000110110565000220111665000190113865000090115765000200116665000290118665000100121565000110122508200080123698000830124422-15-qucosa-161943cr |||||||||||150101s2015 xx eng aeng nurn:nbn:de:bsz:15-qucosa-161943 aStoppe, Sebastian aHow predictable are the Academy Awards? bUniversitätsbibliothek Leipzigc2015g(issued 2015-03-06)9(created 2015)3 aBy conducting an explorative study it is tried to determine whether a sample of film enthusiasts can produce a similar result in judging for the 87th Academy Awards for movies in 2014 like the actual Academy members or not. An online survey has been created and the votes cast by the participants have been tabulated. It can be shown that the results of the simulated awards voting in the survey are quite similar to the actual Academy decision. However, additional adjustments and further studies are recommended to ensure the results. aworkingPaper41uhttps://nbn-resolving.org/urn:nbn:de:bsz:15-qucosa-161943zOnline-Zugriff 4aAcademy Awards. Rangfolgewahl 4aWahlzettel 4aeinfache Mehrheit 4aVorhersagbarkeit 4aFilm 4aVereinigte Staaten von Amerika 4aStudie 4aBefragung 4aAcademy Awards 4ainstant runoff voting 4aballot 4arelative majority 4apredictability 4afilm 4amotion pictures 4aUnited States of America 4astudy 4asurvey0 a791 aurn:nbn:de:bsz:15-qucosa-161943b22csid-22-col-qucosa-adlrcsid-22-col-qucosa \ No newline at end of file diff --git a/module/finc/tests/fixtures/getUrls/no_isil_for_22-15-qucosa-161943_result.json b/module/finc/tests/fixtures/getUrls/no_isil_for_22-15-qucosa-161943_result.json new file mode 100644 index 0000000000000000000000000000000000000000..6f031148fd50c2bc1da67e1071d4fd0b6116eb38 --- /dev/null +++ b/module/finc/tests/fixtures/getUrls/no_isil_for_22-15-qucosa-161943_result.json @@ -0,0 +1,7 @@ +[ + { + "url": "https://nbn-resolving.org/urn:nbn:de:bsz:15-qucosa-161943", + "desc": "Online-Zugriff", + "indicators": "41" + } +] \ No newline at end of file diff --git a/module/finc/tests/unit-tests/src/fincTest/RecordDriver/SolrMarcFincTestCase.php b/module/finc/tests/unit-tests/src/fincTest/RecordDriver/SolrMarcFincTestCase.php index 886eb7aecb2ba0bb3418ce78c8af9fa49a6523cd..1b5e7ec45dd969f10709e13e1aeaef9304dc1b5a 100644 --- a/module/finc/tests/unit-tests/src/fincTest/RecordDriver/SolrMarcFincTestCase.php +++ b/module/finc/tests/unit-tests/src/fincTest/RecordDriver/SolrMarcFincTestCase.php @@ -28,8 +28,9 @@ */ namespace fincTest\RecordDriver; -use VuFindTest\Unit\TestCase as VuFindTestCase; use finc\RecordDriver\SolrMarcFinc as SolrMarcFincDriver; +use finc\RecordDriver\SolrMarcFincTrait; +use VuFindTest\Unit\TestCase as VuFindTestCase; /** * SolrMarcTestCase @@ -38,6 +39,7 @@ use finc\RecordDriver\SolrMarcFinc as SolrMarcFincDriver; * @package finc * @author Guenter Hipler <guenter.hipler@unibas.ch> * @author Frank Morgner <morgnerf@ub.uni-leipzig.de> + * @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 */ @@ -73,11 +75,62 @@ class SolrMarcFincTestCase extends VuFindTestCase * * @return array */ - protected function getFixtureData($file) + protected function getFixtureData($file, $decode = true) + { + $content = file_get_contents(realpath(FINC_TEST_FIXTURES . '/' . $file)); + return $decode ? json_decode($content, true) : $content; + } + + /** + * @return void + */ + public function testGetURLs() + { + // although not current isil, link for IIIF should be shown based on indicator 2 #20600 + $this->runGetURLsTestCase('IIIF_0-1763648745'); + $this->runGetURLsTestCase('IIIF_0-1763648745', ['DE-15']); + + // Record 0-1497294835 with isil of DE-15 in fullrecord (subfield 9) results in empty array for adlr #18915 + $this->runGetURLsTestCase('isil_with_DE-15_0-1497294835', ['DE-15-FID'], []); + $this->runGetURLsTestCase('isil_with_DE-15_0-1497294835', ['FID-MEDIEN-DE-15'], []); + // .. but is shown in DE-15 + $this->runGetURLsTestCase('isil_with_DE-15_0-1497294835', ['DE-15']); + + // Record 0-1497294835 has no given isil in fullrecord (subfield 9) => shown as default / fallback #18915 + $this->runGetURLsTestCase('no_isil_for_22-15-qucosa-161943', ['DE-15-FID']); + $this->runGetURLsTestCase('no_isil_for_22-15-qucosa-161943', ['DE-15']); + } + + protected function getDriverMock() + { + return new SolrMarcFincDriverMock(); + } + + /** + * @return void + */ + public function runGetURLsTestCase(string $testCase, array $isils = [], $expectedResult = null) + { + $driver = $this->getDriverMock(); + $driver->setFullRecord($this->getFixtureData("getUrls/{$testCase}_fullrecord.txt", false)); + $driver->setIsils($isils); + $actualResult = $driver->getURLs(); + $expectedResult = $expectedResult ?? $this->getFixtureData("/getUrls/{$testCase}_result.json"); + $this->assertEquals($expectedResult, $actualResult); + } +} + +class SolrMarcFincDriverMock extends SolrMarcFincDriver +{ + use SolrMarcFincTrait; + + public function setFullRecord(string $fullrecord) + { + $this->fields['fullrecord'] = $fullrecord; + } + + public function setIsils(array $isils) { - return json_decode( - file_get_contents(realpath(FINC_TEST_FIXTURES . '/' . $file)), - true - ); + $this->isil = $isils; } } diff --git a/module/finc/tests/unit-tests/src/fincTest/View/Helper/Root/ExternalCatalogueLinkTest.php b/module/finc/tests/unit-tests/src/fincTest/View/Helper/Root/ExternalCatalogueLinkTest.php index 5a2559ae84f3c67558be3a6617ed01f400535d02..1d9030923767a914ddd9554a458e9b21deb7f484 100644 --- a/module/finc/tests/unit-tests/src/fincTest/View/Helper/Root/ExternalCatalogueLinkTest.php +++ b/module/finc/tests/unit-tests/src/fincTest/View/Helper/Root/ExternalCatalogueLinkTest.php @@ -57,9 +57,13 @@ class ExternalCatalogueLinkTest extends \VuFindTest\Unit\ViewHelperTestCase = $this->getExternalCatalogueLink( [], $this->getFixture('testexternallink1.json', 'externallink') - )->__invoke($this->getMockDriver()); + )->__invoke($this->getMockDriver( + 'VuFind\RecordDriver\SolrDefault', + '0008964455', + '265768233', + '22', + ['DE-14', 'DE-D13'])); - $links = $externalCatalogue->getLinks(); $expectedArray = [ [ "desc" => "DE-D13", @@ -70,7 +74,8 @@ class ExternalCatalogueLinkTest extends \VuFindTest\Unit\ViewHelperTestCase "url" => "http://katalogbeta.slub-dresden.de/id/0008964455/" ] ]; - $this->assertEquals(json_encode($expectedArray), json_encode($links)); + $this->assertEquals(json_encode($expectedArray), json_encode($externalCatalogue->getLinks())); + $externalCatalogue = $this->getExternalCatalogueLink( [], @@ -84,14 +89,13 @@ class ExternalCatalogueLinkTest extends \VuFindTest\Unit\ViewHelperTestCase ['DE-D13', 'DE-540'] ) ); - $links = $externalCatalogue->getLinks(); $expectedArray = [ [ "desc" => "DE-540", "url" => "http://194.94.197.6/libero/WebopacOpenURL.cls?ACTION=DISPLAY&LANG=DE&RID=265768233" ] ]; - $this->assertEquals(json_encode($expectedArray), json_encode($links)); + $this->assertEquals(json_encode($expectedArray), json_encode($externalCatalogue->getLinks())); // @codingStandardsIgnoreEnd } @@ -108,10 +112,10 @@ class ExternalCatalogueLinkTest extends \VuFindTest\Unit\ViewHelperTestCase */ protected function getMockDriver( $class = 'VuFind\RecordDriver\SolrDefault', - $id = '0008964455', - $record_id = '265768233', - $source_id = "0", - $institutions = ["DE-14", "DE-D13"] + $id, + $record_id, + $source_id, + $institutions ) { $driver = $this->getMockBuilder($class) ->setMethods(['getUniqueID', 'getSourceID', 'tryMethod']) @@ -124,14 +128,15 @@ class ExternalCatalogueLinkTest extends \VuFindTest\Unit\ViewHelperTestCase ->method('tryMethod') ->withConsecutive( [$this->equalTo('getInstitutions')], - [$this->equalTo('getRID')], - [$this->equalTo('getPpn')], - [$this->equalTo('getSourceID')] + [$this->equalTo('getField'), $this->equalTo(['record_id'])], + [$this->equalTo('getSourceID')], + [$this->equalTo('getField'), $this->equalTo(['id'])] ) ->willReturnOnConsecutiveCalls( $this->returnValue($institutions), $this->returnValue($record_id), - $this->returnValue($source_id) + $this->returnValue($source_id), + $this->returnValue($id) ); return $driver; } diff --git a/themes/finc-accessibility/js/cart.js b/themes/finc-accessibility/js/cart.js new file mode 100644 index 0000000000000000000000000000000000000000..9fc90e6810a59fd411c5861d382083f1672054af --- /dev/null +++ b/themes/finc-accessibility/js/cart.js @@ -0,0 +1,287 @@ +/*global Cookies, VuFind */ +/*exported cartFormHandler */ + +VuFind.register('cart', function Cart() { + var _COOKIE = 'vufind_cart'; + var _COOKIE_SOURCES = 'vufind_cart_src'; + var _COOKIE_DELIM = "\t"; + var _COOKIE_DOMAIN = false; + var _COOKIE_PATH = '/'; + + function setDomain(domain) { + _COOKIE_DOMAIN = domain; + } + + function setCookiePath(path) { + _COOKIE_PATH = path; + } + + function _uniqueArray(op) { + var ret = []; + for (var i = 0; i < op.length; i++) { + if (ret.indexOf(op[i]) < 0) { + ret.push(op[i]); + } + } + return ret; + } + + function _getItems() { + var items = Cookies.getItem(_COOKIE); + if (items) { + return items.split(_COOKIE_DELIM); + } + return []; + } + function _getSources() { + var items = Cookies.getItem(_COOKIE_SOURCES); + if (items) { + return items.split(_COOKIE_DELIM); + } + return []; + } + function getFullItems() { + var items = _getItems(); + var sources = _getSources(); + var full = []; + if (items.length === 0) { + return []; + } + for (var i = items.length; i--;) { + full[full.length] = sources[items[i].charCodeAt(0) - 65] + '|' + items[i].substr(1); + } + return full; + } + + function hasItem(id, _source) { + var source = _source || VuFind.defaultSearchBackend; + return _getItems().indexOf(String.fromCharCode(65 + _getSources().indexOf(source)) + id) > -1; + } + + function _refreshToggles() { + var $toggleBtns = $('.btn-bookbag-toggle'); + if ($toggleBtns.length > 0) { + $toggleBtns.each(function cartIdEach() { + var $this = $(this); + $this.find('.cart-add,.cart-remove').addClass('hidden'); + if (hasItem($this.data('cart-id'), $this.data('cart-source'))) { + $this.find('.cart-remove').removeClass('hidden'); + } else { + $this.find('.cart-add').removeClass('hidden'); + } + }); + } + } + + function updateCount() { + var items = VuFind.cart.getFullItems(); + $('#cartItems strong').html(items.length); + if (items.length === parseInt(VuFind.translate('bookbagMax'), 10)) { + $('#cartItems .full').removeClass('hidden'); + } else { + $('#cartItems .full').addClass('hidden'); + } + _refreshToggles(); + } + + function addItem(id, _source) { + var source = _source || VuFind.defaultSearchBackend; + var cartItems = _getItems(); + var cartSources = _getSources(); + if (cartItems.length >= parseInt(VuFind.translate('bookbagMax'), 10)) { + return false; + } + var sIndex = cartSources.indexOf(source); + if (sIndex < 0) { + // Add source to source cookie + cartItems[cartItems.length] = String.fromCharCode(65 + cartSources.length) + id; + cartSources[cartSources.length] = source; + Cookies.setItem(_COOKIE_SOURCES, cartSources.join(_COOKIE_DELIM), false, _COOKIE_PATH, _COOKIE_DOMAIN); + } else { + cartItems[cartItems.length] = String.fromCharCode(65 + sIndex) + id; + } + Cookies.setItem(_COOKIE, _uniqueArray(cartItems).join(_COOKIE_DELIM), false, _COOKIE_PATH, _COOKIE_DOMAIN); + updateCount(); + return true; + } + function removeItem(id, source) { + var cartItems = _getItems(); + var cartSources = _getSources(); + // Find + var cartIndex = cartItems.indexOf(String.fromCharCode(65 + cartSources.indexOf(source)) + id); + if (cartIndex > -1) { + var sourceIndex = cartItems[cartIndex].charCodeAt(0) - 65; + var saveSource = false; + for (var i = cartItems.length; i--;) { + if (i === cartIndex) { + continue; + } + // If this source is shared by another, keep it + if (cartItems[i].charCodeAt(0) - 65 === sourceIndex) { + saveSource = true; + break; + } + } + cartItems.splice(cartIndex, 1); + // Remove unused sources + if (!saveSource) { + var oldSources = cartSources.slice(0); + cartSources.splice(sourceIndex, 1); + // Adjust source index characters + for (var j = cartItems.length; j--;) { + var si = cartItems[j].charCodeAt(0) - 65; + var ni = cartSources.indexOf(oldSources[si]); + cartItems[j] = String.fromCharCode(65 + ni) + cartItems[j].substring(1); + } + } + if (cartItems.length > 0) { + Cookies.setItem(_COOKIE, _uniqueArray(cartItems).join(_COOKIE_DELIM), false, _COOKIE_PATH, _COOKIE_DOMAIN); + Cookies.setItem(_COOKIE_SOURCES, _uniqueArray(cartSources).join(_COOKIE_DELIM), false, _COOKIE_PATH, _COOKIE_DOMAIN); + } else { + Cookies.removeItem(_COOKIE, _COOKIE_PATH, _COOKIE_DOMAIN); + Cookies.removeItem(_COOKIE_SOURCES, _COOKIE_PATH, _COOKIE_DOMAIN); + } + updateCount(); + return true; + } + return false; + } + + var _cartNotificationTimeout = false; + function _registerUpdate(_form) { + var $form = typeof _form === 'undefined' + ? $('form[name="bulkActionForm"]') + : $(_form); + $("#updateCart, #bottom_updateCart").unbind('click').click(function cartUpdate() { + var elId = this.id; + var selected = []; + var addToSelected = function processCartFormValues() { + if (-1 === selected.indexOf(this.value)) { + selected.push(this.value); + } + }; + var selectedInForm = $form.find('input[name="ids[]"]:checked'); + var selectedFormAttr = $('input[form="' + $form.attr('id') + '"][name="ids[]"]:checked'); + $(selectedInForm).each(addToSelected); + $(selectedFormAttr).each(addToSelected); + if (selected.length > 0) { + var msg = ""; + var orig = getFullItems(); + $(selected).each(function cartCheckedItemsAdd() { + var data = this.split('|'); + addItem(data[1], data[0]); + }); + var updated = getFullItems(); + var added = updated.length - orig.length; + var inCart = selected.length - added; + msg += VuFind.translate('itemsAddBag', {'%%count%%': added}); + if (updated.length >= parseInt(VuFind.translate('bookbagMax'), 10)) { + msg += "<br/>" + VuFind.translate('bookbagFull'); + } + if (inCart > 0 && orig.length > 0) { + msg += "<br/>" + VuFind.translate('itemsInBag', {'%%count%%': inCart}); + } + $('#' + elId).data('bs.popover').options.content = msg; + $('#cartItems strong').html(updated.length); + } else { + $('#' + elId).data('bs.popover').options.content = VuFind.translate('bulk_noitems_advice'); + } + $('#' + elId).popover('show'); + if (_cartNotificationTimeout !== false) { + clearTimeout(_cartNotificationTimeout); + } + _cartNotificationTimeout = setTimeout(function notificationHide() { + $('#' + elId).popover('hide'); + }, 5000); + return false; + }); + } + + function _registerToggles() { + var $toggleBtns = $('.btn-bookbag-toggle'); + if ($toggleBtns.length > 0) { + $toggleBtns.each(function cartIdEach() { + var $this = $(this); + var currentId = $this.data('cart-id'); + var currentSource = $this.data('cart-source'); + $this.find('.correct').removeClass('correct hidden'); + $this.find('.cart-add').click(function cartAddClick(e) { + e.preventDefault(); + if (addItem(currentId, currentSource)) { + $this.find('.cart-add').addClass('hidden'); + /* #20374 set focus */ + $this.find('.cart-remove').removeClass('hidden').focus(); + /* #20374 set focus - END*/ + } else { + $this.popover({content: VuFind.translate('bookbagFull')}); + setTimeout(function recordCartFullHide() { + $this.popover('hide'); + }, 5000); + } + }); + $this.find('.cart-remove').click(function cartRemoveClick(e) { + e.preventDefault(); + removeItem(currentId, currentSource); + /* #20374 set focus */ + $this.find('.cart-add').removeClass('hidden').focus(); + /* #20374 set focus - END */ + $this.find('.cart-remove').addClass('hidden'); + }); + }); + } + } + + function init() { + // Record buttons + _registerToggles(); + // Search results + _registerUpdate(); + $("#updateCart, #bottom_updateCart").popover({ + content: '', + html: true, + trigger: 'manual', + placement: $(document.body).hasClass('rtl') ? 'left' : 'right' + }); + /* #18034 accessibility: inform screen reader about changes, eventually intregrate by PR into bootstrap - RL */ + var cart = document.getElementById("cartSummary"); + if (cart !== null && cart !== undefined) { + cart.setAttribute("aria-live", "polite"); + cart.setAttribute("aria-atomic", "true"); + } + /* #18034 - END */ + updateCount(); + } + + // Reveal + return { + // Methods + addItem: addItem, + getFullItems: getFullItems, + hasItem: hasItem, + removeItem: removeItem, + setCookiePath: setCookiePath, + setDomain: setDomain, + updateCount: updateCount, + // Init + init: init + }; +}); + +// Building an array and checking indexes prevents a race situation +// We want to prioritize empty over printing +function cartFormHandler(event, data) { + var keys = []; + for (var i in data) { + if (Object.prototype.hasOwnProperty.call(data, i)) { + keys.push(data[i].name); + } + } + if (keys.indexOf('ids[]') === -1) { + return null; + } + if (keys.indexOf('print') > -1) { + return true; + } +} + +document.addEventListener('VuFind.lightbox.closed', VuFind.cart.updateCount, false); diff --git a/themes/finc-accessibility/js/record.js b/themes/finc-accessibility/js/record.js new file mode 100644 index 0000000000000000000000000000000000000000..26da27f6f5f484bf429a4eab12162fc12bf15de8 --- /dev/null +++ b/themes/finc-accessibility/js/record.js @@ -0,0 +1,325 @@ +/*global deparam, getUrlRoot, grecaptcha, recaptchaOnLoad, resetCaptcha, syn_get_widget, userIsLoggedIn, VuFind, setupJumpMenus */ +/*exported ajaxTagUpdate, recordDocReady, refreshTagListCallback */ + +/** + * Functions and event handlers specific to record pages. + */ +function checkRequestIsValid(element, requestType) { + var recordId = element.href.match(/\/Record\/([^/]+)\//)[1]; + var vars = deparam(element.href); + vars.id = recordId; + + var url = VuFind.path + '/AJAX/JSON?' + $.param({ + method: 'checkRequestIsValid', + id: recordId, + requestType: requestType, + data: vars + }); + $.ajax({ + dataType: 'json', + cache: false, + url: url + }) + .done(function checkValidDone(response) { + if (response.data.status) { + $(element).removeClass('disabled') + .attr('title', response.data.msg) + .html('<i class="fa fa-flag" aria-hidden="true"></i> ' + response.data.msg); + } else { + $(element).remove(); + } + }) + .fail(function checkValidFail(/*response*/) { + $(element).remove(); + }); +} + +function setUpCheckRequest() { + $('.checkRequest').each(function checkRequest() { + checkRequestIsValid(this, 'Hold'); + }); + $('.checkStorageRetrievalRequest').each(function checkStorageRetrievalRequest() { + checkRequestIsValid(this, 'StorageRetrievalRequest'); + }); + $('.checkILLRequest').each(function checkILLRequest() { + checkRequestIsValid(this, 'ILLRequest'); + }); +} + +function deleteRecordComment(element, recordId, recordSource, commentId) { + var url = VuFind.path + '/AJAX/JSON?' + $.param({ method: 'deleteRecordComment', id: commentId }); + $.ajax({ + dataType: 'json', + url: url + }) + .done(function deleteCommentDone(/*response*/) { + $($(element).closest('.comment')[0]).remove(); + }); +} + +function refreshCommentList($target, recordId, recordSource) { + var url = VuFind.path + '/AJAX/JSON?' + $.param({ + method: 'getRecordCommentsAsHTML', + id: recordId, + source: recordSource + }); + $.ajax({ + dataType: 'json', + url: url + }) + .done(function refreshCommentListDone(response) { + // Update HTML + var $commentList = $target.find('.comment-list'); + $commentList.empty(); + $commentList.append(response.data.html); + $commentList.find('.delete').unbind('click').click(function commentRefreshDeleteClick() { + var commentId = $(this).attr('id').substr('recordComment'.length); + deleteRecordComment(this, recordId, recordSource, commentId); + return false; + }); + $target.find('.comment-form input[type="submit"]').button('reset'); + resetCaptcha($target); + }); +} + +function registerAjaxCommentRecord(_context) { + var context = typeof _context === "undefined" ? document : _context; + // Form submission + $(context).find('form.comment-form').unbind('submit').submit(function commentFormSubmit() { + var form = this; + var id = form.id.value; + var recordSource = form.source.value; + var url = VuFind.path + '/AJAX/JSON?' + $.param({ method: 'commentRecord' }); + var data = { + comment: form.comment.value, + id: id, + source: recordSource + }; + if (typeof grecaptcha !== 'undefined') { + var recaptcha = $(form).find('.g-recaptcha'); + if (recaptcha.length > 0) { + data['g-recaptcha-response'] = grecaptcha.getResponse(recaptcha.data('captchaId')); + } + } + $.ajax({ + type: 'POST', + url: url, + data: data, + dataType: 'json' + }) + .done(function addCommentDone(/*response, textStatus*/) { + var $form = $(form); + var $tab = $form.closest('.list-tab-content'); + if (!$tab.length) { + $tab = $form.closest('.tab-pane'); + } + refreshCommentList($tab, id, recordSource); + $form.find('textarea[name="comment"]').val(''); + $form.find('input[type="submit"]').button('loading'); + resetCaptcha($form); + }) + .fail(function addCommentFail(response, textStatus) { + if (textStatus === 'abort' || typeof response.responseJSON === 'undefined') { return; } + VuFind.lightbox.alert(response.responseJSON.data, 'danger'); + }); + return false; + }); + // Delete links + $('.delete').click(function commentDeleteClick() { + var commentId = this.id.substr('recordComment'.length); + deleteRecordComment(this, $('.hiddenId').val(), $('.hiddenSource').val(), commentId); + return false; + }); + // Prevent form submit + return false; +} + +function registerTabEvents() { + // Logged in AJAX + registerAjaxCommentRecord(); + // Render recaptcha + recaptchaOnLoad(); + + setUpCheckRequest(); + + VuFind.lightbox.bind('.tab-pane.active'); +} + +function removeHashFromLocation() { + if (window.history.replaceState) { + var href = window.location.href.split('#'); + window.history.replaceState({}, document.title, href[0]); + } else { + window.location.hash = '#'; + } +} + +function ajaxLoadTab($newTab, tabid, setHash) { + // Request the tab via AJAX: + $.ajax({ + url: VuFind.path + getUrlRoot(document.URL) + '/AjaxTab', + type: 'POST', + data: {tab: tabid} + }) + .always(function ajaxLoadTabDone(data) { + if (typeof data === 'object') { + $newTab.html(data.responseText ? data.responseText : VuFind.translate('error_occurred')); + } else { + $newTab.html(data); + } + registerTabEvents(); + if (typeof syn_get_widget === "function") { + syn_get_widget(); + } + if (typeof setHash == 'undefined' || setHash) { + window.location.hash = tabid; + } else { + removeHashFromLocation(); + } + setupJumpMenus($newTab); + }); + return false; +} + +function refreshTagList(_target, _loggedin) { + var loggedin = !!_loggedin || userIsLoggedIn; + var target = _target || document; + var recordId = $(target).find('.hiddenId').val(); + var recordSource = $(target).find('.hiddenSource').val(); + var $tagList = $(target).find('.tagList'); + if ($tagList.length > 0) { + var url = VuFind.path + '/AJAX/JSON?' + $.param({ + method: 'getRecordTags', + id: recordId, + source: recordSource + }); + $.ajax({ + dataType: 'json', + url: url + }) + .done(function getRecordTagsDone(response) { + $tagList.empty(); + $tagList.replaceWith(response.data.html); + if (loggedin) { + $tagList.addClass('loggedin'); + } else { + $tagList.removeClass('loggedin'); + } + }); + } +} +function refreshTagListCallback() { + refreshTagList(false, true); +} + +function ajaxTagUpdate(_link, tag, _remove) { + var link = _link || document; + var remove = _remove || false; + var $target = $(link).closest('.record'); + var recordId = $target.find('.hiddenId').val(); + var recordSource = $target.find('.hiddenSource').val(); + $.ajax({ + url: VuFind.path + '/AJAX/JSON?method=tagRecord', + method: 'POST', + data: { + tag: '"' + tag.replace(/\+/g, ' ') + '"', + id: recordId, + source: recordSource, + remove: remove + } + }) + .always(function tagRecordAlways() { + refreshTagList($target, false); + }); +} + +function getNewRecordTab(tabid) { + return $('<div class="tab-pane ' + tabid + '-tab" id="' + tabid + '" role="tabpanel" tabindex="-1" aria-labelledby="' + tabid + '-tabselector"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> ' + VuFind.translate('loading') + '...</div>'); +} + +function backgroundLoadTab(tabid) { + if ($('.' + tabid + '-tab').length > 0) { + return; + } + var newTab = getNewRecordTab(tabid); + $('.nav-tabs a.' + tabid).closest('.result,.record').find('.tab-content').append(newTab); + return ajaxLoadTab(newTab, tabid, false); +} + +function applyRecordTabHash() { + var activeTab = $('.record-tabs li.active').attr('data-tab'); + var $initiallyActiveTab = $('.record-tabs li.initiallyActive a'); + var newTab = typeof window.location.hash !== 'undefined' + ? window.location.hash.toLowerCase() : ''; + + // Open tab in url hash + if (newTab.length <= 1 || newTab === '#tabnav') { + $initiallyActiveTab.click(); + } else if (newTab.length > 1 && '#' + activeTab !== newTab) { + $('.' + newTab.substr(1) + ' a').click(); + } +} + +$(window).on('hashchange', applyRecordTabHash); + +function recordDocReady() { + $('.record-tabs .nav-tabs a').click(function recordTabsClick() { + var $li = $(this).parent(); + // If it's an active tab, click again to follow to a shareable link. + if ($li.hasClass('active')) { + return true; + } + var tabid = $li.attr('data-tab'); + var $top = $(this).closest('.record-tabs'); + + // accessibility: mark tab controls as selected + $top.find('.record-tab.active').find('a').attr('aria-selected', 'false'); + // accessibility: set information about connection between tab control and tab content + $(this).attr('aria-controls', tabid); + $('#' + tabid + '-tabselector').attr('aria-selected', 'true').attr('aria-controls', tabid); + + // accessibility: set aria-hidden for content panes + $top.find('.tab-pane.active').removeClass('active').attr('aria-hidden', 'true'); + $top.find('.' + tabid + '-tab').addClass('active').attr('aria-hidden', 'false'); + + // if we're flagged to skip AJAX for this tab, we need special behavior: + if ($li.hasClass('noajax')) { + // if this was the initially active tab, we have moved away from it and + // now need to return -- just switch it back on. + if ($li.hasClass('initiallyActive')) { + $(this).tab('show'); + window.location.hash = 'tabnav'; + return false; + } + // otherwise, we need to let the browser follow the link: + return true; + } + $(this).tab('show'); + if ($top.find('.' + tabid + '-tab').length > 0) { + $top.find('.' + tabid + '-tab').addClass('active'); + if ($top.find('#' + tabid ).length) { + $top.find('#' + tabid ).parent().focus(); + } + if ($(this).parent().hasClass('initiallyActive')) { + removeHashFromLocation(); + } else { + window.location.hash = tabid; + } + return false; + } else { + var newTab = getNewRecordTab(tabid).addClass('active'); + $top.find('.tab-content').append(newTab); + if ($top.find('#' + tabid ).length) { + $top.find('#' + tabid ).parent().focus(); + } + return ajaxLoadTab(newTab, tabid, !$(this).parent().hasClass('initiallyActive')); + } + }); + + $('[data-background]').each(function setupBackgroundTabs(index, el) { + backgroundLoadTab(el.className); + }); + + registerTabEvents(); + applyRecordTabHash(); +} diff --git a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js index e10770a83219ba52528d475215ee4a0c8b96d175..9462f550c7f0d71ac6de52ed7987fe5831e5d3fe 100644 --- a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js +++ b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js @@ -1,4 +1,335 @@ /*! 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 +!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" + }) + }) + }; + $.fn.collapse.Constructor.prototype.show = function () { + if (!this.transitioning && !this.$element.hasClass("in")) { + var t, e = this.$parent && this.$parent.children(".panel").children(".in, .collapsing"); + if (!(e && e.length && (t = e.data("bs.collapse")) && t.transitioning)) { + var i = $.Event("show.bs.collapse"); + if (this.$element.trigger(i), !i.isDefaultPrevented()) { + e && e.length && (l.call(e, "hide"), t || e.data("bs.collapse", null)); + var o = this.dimension(); + this.$element.removeClass("collapse").addClass("collapsing")[o](0); + // don't add aria-expanded to collapsing element in side facets + if (this.$element.attr('id') === undefined || !this.$element.attr('id').startsWith("side-collapse")) { + this.$element.attr("aria-expanded", !0) + } + this.$trigger.removeClass("collapsed").attr("aria-expanded", !0), this.transitioning = 1; + var n = function () { + this.$element.removeClass("collapsing").addClass("collapse in")[o](""), this.transitioning = 0, this.$element.trigger("shown.bs.collapse") + }; + if (!$.support.transition) return n.call(this); + var s = $.camelCase(["scroll", o].join("-")); + this.$element.one("bsTransitionEnd", $.proxy(n, this)).emulateTransitionEnd(350)[o](this.$element[0][s]) + } + } + } + }; + $.fn.collapse.Constructor.prototype.hide = function () { + if (!this.transitioning && this.$element.hasClass("in")) { + var t = $.Event("hide.bs.collapse"); + if (this.$element.trigger(t), !t.isDefaultPrevented()) { + var e = this.dimension(); + this.$element[e](this.$element[e]())[0].offsetHeight, this.$element.addClass("collapsing").removeClass("collapse in"); + + // don't add aria-expanded to collapsing element in side facets + if (this.$element.attr('id') === undefined || !this.$element.attr('id').startsWith("side-collapse")) { + this.$element.attr("aria-expanded", !1) + } + + this.$trigger.addClass("collapsed").attr("aria-expanded", !1), this.transitioning = 1; + var i = function () { + this.transitioning = 0, this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse") + }; + if (!$.support.transition) return i.call(this); + this.$element[e](0).one("bsTransitionEnd", $.proxy(i, this)).emulateTransitionEnd(350) + } + } + }; + $.fn.collapse.Constructor.prototype.addAriaAndCollapsedClass = function (t, e) { + // don't add aria-expanded to collapsing element in side facets + if (t.attr('id') === undefined || !t.attr('id').startsWith("side-collapse")) { + var i = t.hasClass("in"); + t.attr("aria-expanded", i); + } + e.toggleClass("collapsed", !i).attr("aria-expanded", i); + }; + 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); + +$(document).on('keydown', '.dropdown-abort', function(e) { + if(e.which == 13) { + let toggle = $(this).parent().parent().parent().find('.dropdown-toggle'); + if(toggle.not(this).length){ + toggle.focus(); + } + } +}); +$(document).on('keyup', '.dropdown-abort', function(e) { + if(e.which == 13) { + let toggle = $(this).parent().parent().parent().find('.dropdown-toggle'); + if(toggle.not(this).length){ + toggle.dropdown("toggle"); + } + } +}); + +/* restore focus after closing of lightbox */ +$(document).on('click', '.bulkActionButtons input', function(e) { + VuFind.lightbox.setOrigin(e.target); +}); \ 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 index 423c963b4fd7b5fe345689b89a3c94233085d8e4..7717b77ecd987717df53a4ae4de3c44e81629e1d 100644 --- a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js +++ b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js @@ -1,4 +1,335 @@ /*! 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 +!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 = "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" + }) + }) + }; + $.fn.collapse.Constructor.prototype.show = function () { + if (!this.transitioning && !this.$element.hasClass("in")) { + var t, e = this.$parent && this.$parent.children(".panel").children(".in, .collapsing"); + if (!(e && e.length && (t = e.data("bs.collapse")) && t.transitioning)) { + var i = $.Event("show.bs.collapse"); + if (this.$element.trigger(i), !i.isDefaultPrevented()) { + e && e.length && (l.call(e, "hide"), t || e.data("bs.collapse", null)); + var o = this.dimension(); + this.$element.removeClass("collapse").addClass("collapsing")[o](0); + // don't add aria-expanded to collapsing element in side facets + if (this.$element.attr('id') === undefined || !this.$element.attr('id').startsWith("side-collapse")) { + this.$element.attr("aria-expanded", !0) + } + this.$trigger.removeClass("collapsed").attr("aria-expanded", !0), this.transitioning = 1; + var n = function () { + this.$element.removeClass("collapsing").addClass("collapse in")[o](""), this.transitioning = 0, this.$element.trigger("shown.bs.collapse") + }; + if (!$.support.transition) return n.call(this); + var s = $.camelCase(["scroll", o].join("-")); + this.$element.one("bsTransitionEnd", $.proxy(n, this)).emulateTransitionEnd(350)[o](this.$element[0][s]) + } + } + } + }; + $.fn.collapse.Constructor.prototype.hide = function () { + if (!this.transitioning && this.$element.hasClass("in")) { + var t = $.Event("hide.bs.collapse"); + if (this.$element.trigger(t), !t.isDefaultPrevented()) { + var e = this.dimension(); + this.$element[e](this.$element[e]())[0].offsetHeight, this.$element.addClass("collapsing").removeClass("collapse in"); + + // don't add aria-expanded to collapsing element in side facets + if (this.$element.attr('id') === undefined || !this.$element.attr('id').startsWith("side-collapse")) { + this.$element.attr("aria-expanded", !1) + } + + this.$trigger.addClass("collapsed").attr("aria-expanded", !1), this.transitioning = 1; + var i = function () { + this.transitioning = 0, this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse") + }; + if (!$.support.transition) return i.call(this); + this.$element[e](0).one("bsTransitionEnd", $.proxy(i, this)).emulateTransitionEnd(350) + } + } + }; + $.fn.collapse.Constructor.prototype.addAriaAndCollapsedClass = function (t, e) { + // don't add aria-expanded to collapsing element in side facets + if (t.attr('id') === undefined || !t.attr('id').startsWith("side-collapse")) { + var i = t.hasClass("in"); + t.attr("aria-expanded", i); + } + e.toggleClass("collapsed", !i).attr("aria-expanded", i); + }; + 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); + +$(document).on('keydown', '.dropdown-abort', function(e) { + if(e.which == 13) { + let toggle = $(this).parent().parent().parent().find('.dropdown-toggle'); + if(toggle.not(this).length){ + toggle.focus(); + } + } +}); +$(document).on('keyup', '.dropdown-abort', function(e) { + if(e.which == 13) { + let toggle = $(this).parent().parent().parent().find('.dropdown-toggle'); + if(toggle.not(this).length){ + toggle.dropdown("toggle"); + } + } +}); + +/* restore focus after closing of lightbox */ +$(document).on('click', '.bulkActionButtons input', function(e) { + VuFind.lightbox.setOrigin(e.target); +}); \ No newline at end of file diff --git a/themes/finc-accessibility/scss/compiled.scss b/themes/finc-accessibility/scss/compiled.scss index 766002b57789f053a07aea5a5099e79b2f515732..db481aca78be66dffc9e30ba18c9a77e01996dbd 100644 --- a/themes/finc-accessibility/scss/compiled.scss +++ b/themes/finc-accessibility/scss/compiled.scss @@ -54,4 +54,10 @@ a.remove-filter { display: flex; width: 100%; +} + +.record-tab.active{ + .load-tab-content { + display: none; + } } \ No newline at end of file diff --git a/themes/finc-accessibility/templates/Auth/AbstractBase/login.phtml b/themes/finc-accessibility/templates/Auth/AbstractBase/login.phtml new file mode 100644 index 0000000000000000000000000000000000000000..7a7764f9287be9baa6b4dc3f3b3c83ababcf7843 --- /dev/null +++ b/themes/finc-accessibility/templates/Auth/AbstractBase/login.phtml @@ -0,0 +1,22 @@ +<!-- finc-accessibility: auth - abstractbase - login.phtml --> +<?php $account = $this->auth()->getManager(); ?> +<?php $sessionInitiator = $account->getSessionInitiator($this->serverUrl($this->url('myresearch-home'))); ?> +<?php if (!$sessionInitiator): // display default login form if no login URL provided ?> + <form method="post" action="<?=$this->url('myresearch-home')?>" name="loginForm" class="form-login"> + <?=$this->auth()->getLoginFields()?> + <input type="hidden" name="auth_method" value="<?=$account->getAuthMethod()?>"> + <input type="hidden" name="csrf" value="<?=$this->escapeHtmlAttr($account->getCsrfHash())?>" /> + <div class="form-group"> + <input class="btn btn-primary" type="submit" name="processLogin" aria-label="<?= $this->transEsc("Login-to-account") ?>" value="<?=$this->transEsc('Login')?>"> + <?php if ($account->supportsCreation()): ?> + <a class="btn btn-link createAccountLink" href="<?=$this->url('myresearch-account') ?>?auth_method=<?=$account->getAuthMethod()?>"><?=$this->transEsc('Create New Account')?></a> + <?php endif; ?> + <?php if ($account->supportsRecovery()): ?> + <a class="btn btn-link" href="<?=$this->url('myresearch-recover') ?>?auth_method=<?=$account->getAuthMethod()?>"><?=$this->transEsc('Forgot Password')?></a> + <?php endif; ?> + </div> + </form> +<?php else: ?> + <a href="<?=$this->escapeHtmlAttr($sessionInitiator)?>" class="btn btn-link" data-lightbox-ignore><?=$this->transEsc("Institutional Login")?></a> +<?php endif; ?> +<!-- finc-accessibility: auth - abstractbase - login.phtml - END --> diff --git a/themes/finc-accessibility/templates/Auth/AbstractBase/newpassword.phtml b/themes/finc-accessibility/templates/Auth/AbstractBase/newpassword.phtml index a6715a36d31e45240fcad16d7b7390afe6b74ab6..37a20857e5d2718ae4d2362329bd7f7c8c8c7c77 100644 --- a/themes/finc-accessibility/templates/Auth/AbstractBase/newpassword.phtml +++ b/themes/finc-accessibility/templates/Auth/AbstractBase/newpassword.phtml @@ -8,9 +8,9 @@ <?php endif; ?> <?php if (isset($this->verifyold) && $this->verifyold || isset($this->oldpwd)): ?> <div class="form-group"> - <label class="control-label"><?=$this->transEsc('old_password') ?>:</label> - <input id="current-password" type="password" name="oldpwd" class="form-control" autocomplete="current-password"/> - <div class="help-block with-errors"></div> + <label for="current-password" class="control-label"><?=$this->transEsc('old_password') ?>:</label> + <input id="current-password" type="password" name="oldpwd" class="form-control" aria-describedby="current-password-error" autocomplete="current-password" required aria-required="true" /> + <div id="current-password-error" class="help-block with-errors"></div> </div> <?php endif; ?> <?php @@ -26,21 +26,22 @@ } ?> <div class="form-group"> - <label class="control-label"><?=$this->transEsc('new_password') ?>:</label> + <label for="password" class="control-label"><?=$this->transEsc('new_password') ?>:</label> <input type="password" id="password" name="password" class="form-control" required aria-required="true" <?=isset($this->passwordPolicy['minLength']) ? ' data-minlength="' . $this->passwordPolicy['minLength'] . '" data-minlength-error="' . $this->escapeHtmlAttr($this->translate('password_minimum_length', ['%%minlength%%' => $this->passwordPolicy['minLength']])) . '"' : '' ?> <?=isset($this->passwordPolicy['maxLength']) ? ' maxlength="' . $this->passwordPolicy['maxLength'] . '"' : '' ?> <?=$pattern ? ' pattern="' . $pattern . '"' : '' ?> autocomplete="new-password" + aria-describedby="password-error" /> <?php if ($this->passwordPolicy['hint']): ?> <div class="help-block"><?=$this->transEsc($this->passwordPolicy['hint']) ?></div> <?php endif; ?> - <div class="help-block with-errors"></div> + <div id="password-error" class="help-block with-errors"></div> </div> <div class="form-group"> - <label class="control-label"><?=$this->transEsc('confirm_new_password') ?>:</label> - <input type="password" name="password2" class="form-control" required aria-required="true" data-match="#password" data-match-error="<?=$this->escapeHtmlAttr($this->translate('Passwords do not match'))?>" autocomplete="new-password"/> - <div class="help-block with-errors"></div> + <label for="password2" class="control-label"><?=$this->transEsc('confirm_new_password') ?>:</label> + <input id="password2" type="password" name="password2" class="form-control" required aria-required="true" data-match="#password" data-match-error="<?=$this->escapeHtmlAttr($this->translate('Passwords do not match'))?>" autocomplete="new-password" aria-describedby="password2-error"/> + <div id="password2-error" class="help-block with-errors"></div> </div> <!-- finc-accessibility: Auth - AbstractBase - newpassword - END --> \ No newline at end of file diff --git a/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml b/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml index b4099695aacfd3feb94bd97e67246300908d6a64..1b1f8adcbba51d11ecdae3e1785993b1e8cfff98 100644 --- a/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml +++ b/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml @@ -30,7 +30,7 @@ ]) ?> <?php endforeach; ?> <?php if (empty($this->cluster['list'])): ?> - <div class="facet"><?=$this->transEsc('facet_list_empty')?></div> + <span class="facet"><?=$this->transEsc('facet_list_empty')?></span> <?php endif; ?> <?php /* LESS and SEE MORE links */ ?> @@ -42,9 +42,11 @@ $moreUrl .= '&baseUriExtra=' . urlencode($this->baseUriExtra); } ?> + <li> <a class="facet narrow-toggle <?=$moreClass ?>" data-lightbox href="<?=$moreUrl ?>" rel="nofollow"> <span class="text"><?=$this->transEsc('see all')?> ...</span> </a> + </li> <?php endif; ?> <li class="facet narrow-toggle <?=$moreClass ?>"> <a class="text" href="#" onclick="event.stopImmediatePropagation(); return lessFacets('narrowGroupHidden-<?=$this->escapeHtmlAttr($this->title) ?>');"> diff --git a/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml b/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml index 2abcc73899ebf11bd294537db651a089cf64b21d..3d2c763fce3e185c1e9f000fd9d06f4856a12c75 100644 --- a/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml +++ b/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml @@ -1,7 +1,7 @@ <!-- finc-accessibility - Recommend - SideFacets - filter-list.phtml --> <?php /* #18509 copied from bootstrap for adding language tags to displayText */ ?> <div class="facet-group active-filters"> - <div class="title"><?=$this->transEsc('Remove Filters')?></div> + <div class="title"><?=$this->transEsc('Remove Filters')?> <span class="sr-only"><?=$this->transEsc('facet_deselect_hint') ?></span></div> <ul> <?php foreach ($filterList as $field => $filters): ?> <?php foreach ($filters as $i => $filter): ?> diff --git a/themes/finc-accessibility/templates/RecordTab/toc.phtml b/themes/finc-accessibility/templates/RecordTab/toc.phtml deleted file mode 100644 index 6e71822f85068fe2a473bbc3071afd496220f6f5..0000000000000000000000000000000000000000 --- a/themes/finc-accessibility/templates/RecordTab/toc.phtml +++ /dev/null @@ -1,35 +0,0 @@ -<!-- finc-accessibility: RecordTab - toc --> -<?php /** - * copy of VuFind-version, adds empty lang-attribute to <li>s - */ -?> -?> -<?php - // Set page title. - $this->headTitle($this->translate('Table of Contents') . ': ' . $this->driver->getBreadcrumb()); - - $toc = $this->tab->getContent(); - if (empty($toc)) { - $driverToc = $this->driver->getTOC(); - if (!empty($driverToc)) { - $toc['RecordDriver'] = $driverToc; - } - } -?> -<?php if (!empty($toc)): ?> - <strong><?=$this->transEsc('Table of Contents')?>: </strong> - <?php foreach ($toc as $provider => $content): ?> - <?php if (!is_array($content)): // treat non-array content as raw HTML ?> - <?=$content?> - <?php else: ?> - <ul class="toc"> - <?php foreach ($content as $line): ?> - <li lang=""><?=$this->escapeHtml($line)?></li> - <?php endforeach; ?> - </ul> - <?php endif; ?> - <?php endforeach; ?> -<?php else: ?> - <?=$this->transEsc('Table of Contents unavailable')?>. -<?php endif; ?> -<!-- finc-accessibility: RecordTab - toc - END --> \ No newline at end of file diff --git a/themes/finc/js/cart-finc.js b/themes/finc/js/cart-finc.js deleted file mode 100644 index 0fbaf1015f44cd429d87a2f1db9a0f5745c5c320..0000000000000000000000000000000000000000 --- a/themes/finc/js/cart-finc.js +++ /dev/null @@ -1,8 +0,0 @@ -/* #18034 - inform screen reader about changes, eventually intregrate by PR into bootstrap cart.js - RL */ -$(document).ready(function() { - var cart = document.getElementById("cartSummary"); - if (cart != null) { - cart.setAttribute("aria-live", "polite"); - cart.setAttribute("aria-atomic", "true"); - } -}); diff --git a/themes/finc/js/covers.js b/themes/finc/js/covers.js index f54f93b677c941e87ad4fc3a0406e43f54d20aba..3eb309ef70b9765951f99e163e3185e1084aca2d 100644 --- a/themes/finc/js/covers.js +++ b/themes/finc/js/covers.js @@ -50,7 +50,8 @@ function loadCoverByElement(data, element) { method: 'GET', data: data, element: element, - success: coverCallback + success: coverCallback, + error: spinner.hide() }); } diff --git a/themes/finc/js/lightbox.js b/themes/finc/js/lightbox.js index 108712feda24df15a4e3a6fe1a104992589d21da..8cdfbe87fe9d0cec2d198dd47dc4231c84df65ca 100644 --- a/themes/finc/js/lightbox.js +++ b/themes/finc/js/lightbox.js @@ -1,4 +1,6 @@ -/*global grecaptcha, recaptchaOnLoad, resetCaptcha, VuFind, TEMPORARY BARF CK */ +/*global recaptchaOnLoad, resetCaptcha, VuFind, userIsLoggedIn:true */ +// TEMPORARY BARF CK +/*jshint strict: false */ VuFind.register('lightbox', function Lightbox() { // State var _originalUrl = false; @@ -15,7 +17,7 @@ VuFind.register('lightbox', function Lightbox() { function _html(content) { _modalBody.html(content); // Set or update title if we have one - var $h2 = _modalBody.find("h2:first-of-type"); + var $h2 = _modalBody.find('h2:first-of-type'); if (_lightboxTitle && $h2) { $h2.text(_lightboxTitle); } @@ -42,8 +44,8 @@ VuFind.register('lightbox', function Lightbox() { // Public: Present an alert function showAlert(message, _type) { var type = _type || 'info'; - _html('<div class="flash-message alert alert-' + type + '" role="alert">' + message + '</div>' - + '<button class="btn btn-default" data-dismiss="modal">' + VuFind.translate('close') + '</button>'); + _html('<div class="flash-message alert alert-' + type + '" role="alert">' + message + '</div>' + + '<button class="btn btn-default" data-dismiss="modal">' + VuFind.translate('close') + '</button>'); _modal.modal('show'); } function flashMessage(message, _type) { @@ -67,7 +69,7 @@ VuFind.register('lightbox', function Lightbox() { var _constrainLink; var _formSubmit; function render(content) { - if (typeof content !== "string") { + if (typeof content !== 'string') { return; } // Isolate successes @@ -112,6 +114,16 @@ VuFind.register('lightbox', function Lightbox() { $('#modal').find('.checkbox-select-item').change(function lbSelectAllDisable() { $(this).closest('.modal-body').find('.checkbox-select-all').prop('checked', false); }); + + // #19695 accessibility: set id for aria-label + if (_modalBody.find('h1').length) { + _modalBody.find('h1:first').attr('id', 'modal-title'); + $('#modal').attr('aria-labelledby', 'modal-title'); + } else if (_modalBody.find('h2').length) { + _modalBody.find('h2:first').attr('id', 'modal-title'); + $('#modal').attr('aria-labelledby', 'modal-title'); + } + // Recaptcha recaptchaOnLoad(); } @@ -128,9 +140,7 @@ VuFind.register('lightbox', function Lightbox() { // Add lightbox GET parameter if (!obj.url.match(/layout=lightbox/)) { var parts = obj.url.split('#'); - obj.url = parts[0].indexOf('?') < 0 - ? parts[0] + '?' - : parts[0] + '&'; + obj.url = parts[0].indexOf('?') < 0 ? parts[0] + '?' : parts[0] + '&'; obj.url += 'layout=lightbox'; if (_currentUrl) { obj.url += '&lbreferer=' + encodeURIComponent(_currentUrl); @@ -168,8 +178,7 @@ VuFind.register('lightbox', function Lightbox() { // - not a failed login if ( obj.method && ( - obj.url.match(/catalogLogin/) - || obj.url.match(/MyResearch\/(?!Bulk|Delete|Recover)/) + obj.url.match(/catalogLogin/) || obj.url.match(/MyResearch\/(?!Bulk|Delete|Recover)/) ) && flashMessages.length === 0 ) { @@ -182,7 +191,8 @@ VuFind.register('lightbox', function Lightbox() { VuFind.refreshPage(); } return false; - } else { + /* FINC-specific: avoid page reload after login if param is given - show account links by javascript */ + } else if (new URL(_originalUrl).searchParams.get('refreshOnClose') !== 'false' || !displayLogin()) { VuFind.lightbox.refreshOnClose = true; } _currentUrl = _originalUrl; // Now that we're logged in, where were we? @@ -202,6 +212,22 @@ VuFind.register('lightbox', function Lightbox() { ajax({ url: _currentUrl || _originalUrl }); } + /** + * Finc specific: Login without Page Reload + */ + function displayLogin() { + try { + if ($('#loginOptions').length && $('.logoutOptions').length) { + userIsLoggedIn = true; + $('#loginOptions').hide(); + $('.logoutOptions').removeClass('hidden'); + return true; + } + } catch (e) { /* Empty */ } + + return false; + } + /** * Evaluate a callback */ @@ -243,22 +269,23 @@ VuFind.register('lightbox', function Lightbox() { VuFind.lightbox.reset(); } var $link = $(this); - if (typeof $link.data("lightboxIgnore") != "undefined" - || typeof $link.attr("href") === "undefined" - || $link.attr("href").charAt(0) === "#" - || $link.attr("href").match(/^[a-zA-Z]+:[^/]/) // ignore resource identifiers (mailto:, tel:, etc.) - || (typeof $link.attr("target") !== "undefined" - && ( - $link.attr("target").toLowerCase() === "_new" - || $link.attr("target").toLowerCase() === "new" - )) + if (typeof $link.data('lightboxIgnore') !== 'undefined' || + typeof $link.attr('href') === 'undefined' || + $link.attr('href').charAt(0) === '#' || + $link.attr('href').match(/^[a-zA-Z]+:[^/]/) || // ignore resource identifiers (mailto:, tel:, etc.) + (typeof $link.attr('target') !== 'undefined' && + ( + $link.attr('target').toLowerCase() === '_new' || + $link.attr('target').toLowerCase() === 'new' + ) + ) ) { return true; } if (this.href.length > 1) { event.preventDefault(); var obj = {url: $(this).data('lightboxHref') || this.href}; - if ("string" === typeof $(this).data('lightboxPost')) { + if ('string' === typeof $(this).data('lightboxPost')) { obj.type = 'POST'; obj.data = $(this).data('lightboxPost'); } @@ -409,6 +436,9 @@ VuFind.register('lightbox', function Lightbox() { } } } + function setOrigin(origin) { + _origin = origin; + } function onKeydown(e) { if (event.keyCode === 27) { // esc close(); @@ -466,7 +496,6 @@ VuFind.register('lightbox', function Lightbox() { }); }); } - function reset() { _html(VuFind.translate('loading') + '...'); _originalUrl = false; @@ -490,8 +519,9 @@ VuFind.register('lightbox', function Lightbox() { VuFind.lightbox.reset(); _emit('VuFind.lightbox.closed'); // set focus back on launching element - if (_origin != "undefined") + if (_origin !== null && _origin !== 'undefined') { _origin.focus(); + } }); _modal.on("shown.bs.modal", function lightboxShown() { bindFocus(); @@ -522,6 +552,7 @@ VuFind.register('lightbox', function Lightbox() { render: render, // Reset reset: reset, + setOrigin: setOrigin, // Init init: init }; diff --git a/themes/finc/scss/compiled.scss b/themes/finc/scss/compiled.scss index a7fbd375f82075f348503611d72770e940b5b340..49247edfae1c87f3c8cb1f3bc5b6ccc7a9de28c9 100644 --- a/themes/finc/scss/compiled.scss +++ b/themes/finc/scss/compiled.scss @@ -1265,11 +1265,23 @@ table.collapse.in { margin-top: ($grid-gutter-width / 2); } +.margin-t-half { + margin-top: ($grid-gutter-width / 4); +} + ////// NO margin bottom .no-margin-btm { margin-bottom: 0; } +.margin-btm { + margin-bottom: ($grid-gutter-width / 2); +} + +.margin-btm-half { + margin-bottom: ($grid-gutter-width / 4); +} + ////// NO margin left .no-margin-l { margin-left: 0; @@ -2579,6 +2591,8 @@ footer { @media (max-width: $screen-xs-max) { border: $border-default-styles; + line-height: 1.5; + margin-top: 29px; } @media (min-width: $screen-md-min) { @@ -3305,3 +3319,14 @@ input { } } // CHANNELS - END + +// ZEND-FORMS + +#dds-form { + + label[data-required="true"][for]::after { + content: ' *'; + } +} + +// ZEND-FORMS - END diff --git a/themes/finc/templates/Recommend/EbscoResults.phtml b/themes/finc/templates/Recommend/EbscoResults.phtml index 2801644566d2ab48ed2af231dea8c7941f3daf61..253b18944ffacebbbb4811444c50dd5308a4b9f8 100644 --- a/themes/finc/templates/Recommend/EbscoResults.phtml +++ b/themes/finc/templates/Recommend/EbscoResults.phtml @@ -13,15 +13,17 @@ if (is_array($data['results']) && count($data['results']) > 0): ?> <span class="badge"> <?= $this->escapeHtml($result['hits']) ?> </span> - <a href="<?= $this->escapeHtmlAttr($result['url']) ?>" target="_blank"> - <?= $this->escapeHtml($this->truncate($this->transEsc('Ebsco::'.$result['database']), 90)) ?> - </a> + <?= $this->externalLink( + $this->escapeHtmlAttr($result['url']), + $this->truncate($this->translate('Ebsco::'.$result['database']), 90) + ) ?> </div> <?php endforeach; ?> - <a class="facet narrow-toggle" href="<?=$this->escapeHtmlAttr($data['hits_total_url'])?>" target="_blank" rel="nofollow"> - <?=$this->transEsc('more')?> ... - </a> - + <?= $this->externalLink( + $data['hits_total_url'], + $this->translate('more'), + ['class' => 'facet narrow-toggle'] + ) ?> </div> </div> <?php endif; ?> diff --git a/themes/finc/templates/Recommend/SideFacets.phtml b/themes/finc/templates/Recommend/SideFacets.phtml index 361ead35a1593f0ee60b1fa94c71b399d30b988a..3e524b476825eedf817ba8fa2c8b110c3aadf186 100644 --- a/themes/finc/templates/Recommend/SideFacets.phtml +++ b/themes/finc/templates/Recommend/SideFacets.phtml @@ -55,9 +55,9 @@ if ($hierarchicalFacets) { <?php if (!empty($sideFacetSet) && $results->getResultTotal() > 0): ?> <?php foreach ($sideFacetSet as $title => $cluster): ?> <div class="facet-group" id="side-panel-<?=$this->escapeHtmlAttr($title)?>"> - <button class="title<?php if(in_array($title, $collapsedFacets)): ?> collapsed<?php endif ?>" data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($title) ?>" > - <?=$this->transEsc($cluster['label'])?> - </button> + <a <?php if(in_array($title, $collapsedFacets)): ?>class="title collapsed" aria-expanded="false"<?php else: ?>class="title" aria-expanded="true"<?php endif ?> data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($title) ?>" > + <?=$this->transEsc($cluster['label'])?> <span class="sr-only"><?=$this->transEsc('facet_select_hint') ?></span> + </a> <ul id="side-collapse-<?=$this->escapeHtmlAttr($title)?>" class="collapse<?php if (!in_array($title, $collapsedFacets)): ?> in<?php endif ?>"> <?=$this->context($this)->renderInContext( 'Recommend/SideFacets/facet.phtml', diff --git a/themes/finc/templates/Recommend/SideFacets/range-slider.phtml b/themes/finc/templates/Recommend/SideFacets/range-slider.phtml index 430be23580f50ce2ba0fccf96c7bb22bdf494db6..31a94bfef9261342ea71876881dca013bad5985f 100644 --- a/themes/finc/templates/Recommend/SideFacets/range-slider.phtml +++ b/themes/finc/templates/Recommend/SideFacets/range-slider.phtml @@ -4,7 +4,7 @@ * slider-container element */ ?> -<div class="facet"> +<li class="facet"> <?php if (!empty($this->facet['values'][0])): ?> <?php $this->sideFacet()->setAppliedFacet($this->transEsc('Skip to facet', ['%%filter_name%%' => $this->transEsc('Year of Publication')]), $this->escapeHtmlAttr($this->title) . 'from')?> <?php elseif (!empty($this->facet['values'][1])): ?> @@ -34,7 +34,6 @@ <?php endif; ?> <input class="btn btn-default" type="submit" value="<?=$this->transEsc('Set')?>"/> </form> -</div> <?php if ($this->facet['type'] == 'date'): ?> <?php $this->headScript()->appendFile('vendor/bootstrap-slider.min.js'); ?> <?php $this->headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); ?> @@ -83,4 +82,5 @@ JS; ?> <?=$this->inlineScript(\Zend\View\Helper\HeadScript::SCRIPT, $script, 'SET'); ?> <?php endif; ?> +</li> <!-- finc - recommend - sidefacets - rangeslider - END --> diff --git a/themes/finc/templates/Recommend/SideFacets/single-facet.phtml b/themes/finc/templates/Recommend/SideFacets/single-facet.phtml index d82632dc70a58abc9da86952b4c25f8a07029149..00a568ce1757f155394f0a9a0dfb49b78b4e6362 100644 --- a/themes/finc/templates/Recommend/SideFacets/single-facet.phtml +++ b/themes/finc/templates/Recommend/SideFacets/single-facet.phtml @@ -52,10 +52,13 @@ data-count="<?=$this->facet['count'] ?>" title="<?php if($this->facet['isApplied']): ?><?=$this->transEsc('applied_filter')?> - <?=$this->transEsc('page_reload_on_deselect_hint', ['%%filter_name%%' => $this->facet['displayText']])?> <?=$this->escapeHtmlAttr($this->facet['displayText']) ?><?php else: ?><?=$this->transEsc('page_reload_on_select_hint', ['%%filter_name%%' => $this->facet['displayText']])?><?php endif;?>"> <?=$displayText ?> + <span class="sr-only"> + <?php if($this->facet['isApplied']): ?>(<?=$this->transEsc('applied_filter')?> - <?=$this->transEsc('page_reload_on_deselect_hint', ['%%filter_name%%' => $this->facet['displayText']])?> <?=$this->escapeHtmlAttr($this->facet['displayText']) ?>)<?php else: ?>(<?=$this->transEsc('page_reload_on_select_hint', ['%%filter_name%%' => $this->facet['displayText']])?>)<?php endif;?> + </span> </a> <?php else: ?> <span class="text"> - <?=$displayText ?> + <?=$displayText ?><span class="sr-only"><?php if($this->facet['isApplied']): ?>(<?=$this->transEsc('applied_filter')?> - <?=$this->transEsc('page_reload_on_deselect_hint', ['%%filter_name%%' => $this->facet['displayText']])?>)<?php else: ?>(<?=$this->transEsc('page_reload_on_select_hint', ['%%filter_name%%' => $this->facet['displayText']])?>)<?php endif;?></span> </span> <?php endif; ?> @@ -67,8 +70,11 @@ <?php if ($hasSubLinks): ?> <?php $excludeURL = $this->urlBase . $this->url->addFacet($this->group, $this->facet['value'], 'NOT'); ?> - <a href="<?=$excludeURL ?>" data-lightbox-ignore class="exclude" title="<?= $this->transEsc('exclude_filter', ['%%filter_name%%' => $this->facet['displayText']]) ?>, <?= $this->transEsc('page_reload_hint') ?>"> + <a href="<?=$excludeURL ?>" data-lightbox-ignore class="exclude" title="<?= $this->transEsc('exclude_filter', ['%%filter_name%%' => $this->facet['displayText']]) ?>, <?= $this->transEsc('page_reload_on_xclude_hint', ['%%filter_name%%' => $this->facet['displayText']]) ?>"> <i class="fa fa-times" aria-hidden="true"></i> + <span class="sr-only"> + <?= $this->transEsc('exclude_filter', ['%%filter_name%%' => $this->facet['displayText']]) ?>, <?= $this->transEsc('page_reload_on_xclude_hint', ['%%filter_name%%' => $this->facet['displayText']]) ?> + </span> </a> <?php endif; ?> diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/core.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/core.phtml index e7b39706ee3f3f2fdd8e46c481d40ecc9c8a5d25..2836a86ebbc501adcc00269c6bb04edd71b2765a 100644 --- a/themes/finc/templates/RecordDriver/DefaultRecord/core.phtml +++ b/themes/finc/templates/RecordDriver/DefaultRecord/core.phtml @@ -46,7 +46,7 @@ */ ?> <?php /* finc: add schema tags for title #13850 - VE */ ?> - <h1 property="name" lang=""><?= $this->escapeHtml(preg_replace('/(\s[\/\.:]\s*)*$/', '', $this->truncate($this->driver->getShortTitle() . ' ' . $this->driver->getSubtitle() . ' ' . $this->driver->getTitleSection(), 100))) ?></h1> + <h1 property="name" lang=""><?=$this->record($this->driver)->getTitleHtml()?></h1> <?php /* #18307 remove summary from core */ ?> diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/data-license.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/data-license.phtml deleted file mode 100644 index d8230b72f63ddea10fbed660a4fd7003156f60a2..0000000000000000000000000000000000000000 --- a/themes/finc/templates/RecordDriver/DefaultRecord/data-license.phtml +++ /dev/null @@ -1,5 +0,0 @@ -<!-- finc: RecordDriver - DefaultRecord - data-license --> -<?php if (!empty($data) && is_array($data)): ?> - <?= $data['name'] ?> (<a href="<?= $data['url'] ?>"><?= $data['url'] ?></a>) -<?php endif; ?> -<!-- finc: RecordDriver - DefaultRecord - data-license - END --> \ No newline at end of file diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/data-multiline.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/data-multiline.phtml new file mode 100644 index 0000000000000000000000000000000000000000..0e1ea67853047559d0e7c9d1588a9cf7a84f7047 --- /dev/null +++ b/themes/finc/templates/RecordDriver/DefaultRecord/data-multiline.phtml @@ -0,0 +1,7 @@ +<!-- finc: RecordDriver - DefaultRecord - data-multiline --> +<?php if (is_array($data)): ?> + <?=implode('<hr class="margin-t-half margin-btm-half" />', $data) ?> +<?php else: ?> + <?= $data ?> +<?php endif; ?> +<!-- finc: RecordDriver - DefaultRecord - data-multiline - END --> diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/data-toc.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/data-toc.phtml new file mode 100644 index 0000000000000000000000000000000000000000..e2c53a3d900f0d10a657f91ebf98844d159259af --- /dev/null +++ b/themes/finc/templates/RecordDriver/DefaultRecord/data-toc.phtml @@ -0,0 +1,7 @@ +<!-- finc: RecordDriver - DefaultRecord - data-toc --> +<ul class="toc"> +<?php foreach ($data as $item): ?> + <li lang=""><?=$this->escapeHtml($item)?></li> +<?php endforeach; ?> +</ul> +<!-- finc: RecordDriver - DefaultRecord - data-toc - END --> \ No newline at end of file diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml index 77bed61f58f3b6a7a2cfc3113ec6a2fbb194be64..30507b95f9bba7a18fa8cdd5d980856dfd166994 100644 --- a/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml +++ b/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml @@ -1,9 +1,9 @@ <?php /* use advanced search if we have multiple issns */ if (is_array($this->lookfor) && count($this->lookfor) > 1) { - $query = '?join=AND&bool0[]=OR'; + $query = '?join=AND&bool0%5B%5D=OR'; foreach ($this->lookfor as $issn) { - $query .= '&lookfor0[]=' . urlencode($issn) . '&type0[]=ISN'; + $query .= '&lookfor0%5B%5D=' . urlencode($issn) . '&type0%5B%5D=ISN'; } } elseif (count($this->lookfor) == 1) { $query = '?lookfor=%22' . urlencode($this->lookfor[0]) . '%22&type=ISN'; diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml index 3c1233c8b90ba4f5341f0a60d79e1a1bfa024bae..39a333fa0798a25e2e271410e11ff8cfedbbd218 100644 --- a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml +++ b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml @@ -125,7 +125,7 @@ $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('list' <?php endforeach; ?> <?php endif; ?> - <?php if (count($this->lists) > 0): ?> + <?php if (!empty($this->lists)): ?> <strong><?=$this->transEsc('Saved in')?>:</strong> <?php $i = 0; foreach ($this->lists as $current): ?> @@ -181,8 +181,13 @@ $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('list' } if (!$this->driver->isCollection()): foreach ($urls as $current): ?> - <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>" class="fulltext" target="new"><i class="fa fa-external-link" aria-hidden="true"></i> <?=($current['url'] == $current['desc']) ? $this->transEsc('Get full text') : $this->escapeHtml($current['desc'])?> - </a> + <?= $this->externalLink( + $this->escapeHtmlAttr($this->proxyUrl($current['url'])), + '<i class="fa fa-external-link" aria-hidden="true"></i>' + . ($current['url'] == $current['desc'] ? $this->transEsc('Get full text') : $this->escapeHtml($current['desc'])), + ['class' => 'fulltext'], + true + ) ?> <?php endforeach; ?> <?php endif; ?> <?php endif; ?> @@ -224,7 +229,13 @@ $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('list' <ul class="dropdown-menu" role="menu" aria-labelledby="<?= $dLabel ?>"> <li> <?php /* #17711 give user feedback and dont reload page after deleting */ ?> - <a href="javascript:document.getElementById('<?=$dLabel?>').focus();" title="<?= $this->transEsc('confirm_delete_brief') ?>" onClick="$.post( + <a href="javascript:document.getElementById('<?=$dLabel?>').focus();" title="<?= $this->transEsc('confirm_delete_brief') ?>" onClick=" + let next = $(this).closest('.result.ajaxItem').next('.result.ajaxItem').find('.del-button'); + if (next.length === 0) { + next = $('[id^=delete_list_items_]').first(); + } + VuFind.lightbox.setOrigin(next); + $.post( '<?= $deleteUrl ?>', { 'delete':'<?= $this->escapeJs($id) ?>', diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-class.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-class.phtml deleted file mode 100644 index c665701859e2e522a6d8516918ad73c3656a7231..0000000000000000000000000000000000000000 --- a/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-class.phtml +++ /dev/null @@ -1,22 +0,0 @@ -<?php -$normalizedValue = preg_replace('/[^a-z0-9]/', '', strtolower($this->value)); - -// Convert normalizedValue to styles -switch ($normalizedValue) { - case 'local': - echo 'fa-home'; - break; - case 'freeonline': - case 'onlinefree': - case 'online': - case 'free': - echo 'fa-globe'; - break; - case 'missing': - echo 'fa-question'; - break; - default: - echo 'fa-book'; - break; -} -?> diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-sprite-class.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-sprite-class.phtml deleted file mode 100644 index 118bd36bdfb8b84448a4f98e89813fa6bb9594ab..0000000000000000000000000000000000000000 --- a/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-sprite-class.phtml +++ /dev/null @@ -1,176 +0,0 @@ -<?php -$normalizedValue = preg_replace('/[^a-z0-9]/', '', strtolower($this->value)); -// Convert normalizedValue to styles -switch ($normalizedValue) { - - //block book - case 'book': - case 'articles': - echo 'book'; - break; - //block general audio - case 'audio': - case 'musicrecording': - case 'record': - case 'soundrecordingmedium': - case 'electronicsoundrecordingmedium': - case 'soundrecording': - echo 'audio'; - break; - //block tape audio - case 'audiotape': - case 'cassette': - case 'soundcassette': - echo 'audiotape'; - break; - //block braille - case 'braille': - echo 'braille'; - break; - //block digital audio - case 'cd': - case 'dvdaudio': - case 'sounddisc': - echo 'cd'; - break; - //block digital video - case 'dvd': - case 'blueraydisc': - case 'dvdvideo': - case 'videodisc': - echo 'dvd'; - break; - //block ebook - case 'ebook': - echo 'ebook'; - break; - //block digital non-book - case 'electronicnewspaper': - case 'newspaperarticle': - case 'newspaper': - case 'textresource': - echo 'newspaper'; - break; - //block manuscripts - case 'manuscript': - case 'nachlass': - echo 'manuscript'; - break; - //block articles - case 'article': - case 'articlearticle': - case 'electronic': - case 'electronicarticle': - case 'electronicresourcedatacarrier': - case 'electronicresourceremoteaccess': - echo 'electronic'; - break; - //block globe - case 'globe': - echo 'globe'; - break; - //block kit - case 'kit': - echo 'kit'; - break; - //block journal - case 'journal': - case 'journalnewspaper': - case 'serial': - echo 'journal'; - break; - //block ejournal - case 'electronicjournal': - case 'electronicserial': - echo 'electronicjournal'; - break; - //block map - case 'map': - case 'atlas': - echo 'map'; - break; - //block microfilm - case 'microfilm': - case 'microfiche': - case 'microform': - echo 'microfilm'; - break; - //block musical score - case 'musicalscore': - case 'notatedmusic': - case 'electronicmusicalscore': - echo 'musicalscore'; - break; - //block images - case 'photo': - case 'artprint': - case 'collage': - case 'drawing': - case 'flashcard': - case 'painting': - case 'photonegative': - case 'placard': - case 'print': - case 'sensorimage': - case 'transparency': - echo 'image'; - break; - //block physical object - case 'physicalobject': - echo 'physicalobject'; - break; - //block othe rimages - case 'sensorimage': - case 'chart': - echo 'chart'; - break; - //block sets - case 'sets': - echo 'sets'; - break; - //block slide - case 'slide': - echo 'slide'; - break; - //block software - case 'software': - case 'cdrom': - case 'chipcartridge': - case 'disccartridge': - case 'dvdrom': - case 'floppydisk': - case 'tapecartridge': - case 'tapecassette': - case 'tapereel': - echo 'software'; - break; - //block thesis - case 'thesis': - case 'electronicthesis': - echo 'thesis'; - break; - - //block unknown - case 'unknown': - echo 'unknown'; - break; - //block analog video - case 'vhs': - case 'video': - case 'videotape': - case 'videocartridge': - case 'videocassette': - echo 'video'; - break; - //block film - case 'audiovisualmedia': - case 'filmstrip': - case 'motionpicture': - case 'videoreel': - echo 'film'; - break; - //default - default: - echo 'unknown'; - break; -} \ No newline at end of file diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-sprite.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-sprite.phtml deleted file mode 100644 index 279b1a67b82f98758a2450897594c432a7d017d8..0000000000000000000000000000000000000000 --- a/themes/finc/templates/RecordDriver/DefaultRecord/record-icon-sprite.phtml +++ /dev/null @@ -1,11 +0,0 @@ -<?php -$formats = []; -foreach ($this->driver->getFormats() as $format) { - $formats[] = $this->record($this->driver)->getRecordIconClass( - $format, 'record-icon-sprite-class' - ); -} -$formats = array_unique($formats); -asort($formats); -?> -<span class="sprite-media-icon <?=array_pop($formats)?>"></span> \ No newline at end of file diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/result-list.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/result-list.phtml index 878760060d4bcac2728a7ac5cf914eb170648406..b6284f06c352176e5578d77c6f9921858ea8f2f3 100644 --- a/themes/finc/templates/RecordDriver/DefaultRecord/result-list.phtml +++ b/themes/finc/templates/RecordDriver/DefaultRecord/result-list.phtml @@ -94,30 +94,6 @@ if ($cover): <?php endif; ?> <?php endif; ?> - <?php -/* Display information on duplicate records if available */ - if ($dedupData = $this->driver->getDedupData()): ?> - <div class="dedupInformation"> - <?php -$i = 0; - foreach ($dedupData as $source => $current) { - if (++$i == 1) { - ?><span class="currentSource"><a href="<?=$this->recordLink()->getUrl($this->driver)?>"><?=$this->transEsc("source_$source", [], $source)?></a></span><?php - } else { - if ($i == 2) { - ?> <span class="otherSources">(<?=$this->transEsc('Other Sources')?>: <?php - } else { - ?>, <?php - } - ?><a href="<?=$this->recordLink()->getUrl($current['id'])?>"><?=$this->transEsc("source_$source", [], $source)?></a><?php - } - } - if ($i > 1) { - ?>)</span><?php - } ?> - </div> - <?php endif; ?> - <div class="callnumAndLocation ajax-availability hidden"> <?php if ($this->driver->supportsAjaxStatus()): ?> <strong class="hideIfDetailed"><?=$this->transEsc('Call Number')?>:</strong> @@ -160,10 +136,13 @@ $i = 0; <?php if (!is_array($urls)) $urls = []; if (!$this->driver->isCollection()): foreach ($urls as $current): ?> - <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>" class="fulltext" target="new"> - <i class="fa fa-external-link" aria-hidden="true"></i> - <?=($current['url'] == $current['desc']) ? $this->transEsc('Get full text') : $this->escapeHtml($current['desc'])?> - </a> + <?= $this->externalLink( + $this->escapeHtmlAttr($this->proxyUrl($current['url'])), + '<i class="fa fa-external-link" aria-hidden="true"></i>' + . ($current['url'] == $current['desc'] ? $this->transEsc('Get full text') : $this->escapeHtml($current['desc'])), + ['class' => 'fulltext'], + true + ) ?> <br/> <?php endforeach; ?> <?php endif; ?> @@ -209,7 +188,7 @@ $i = 0; <?php if ($this->userlist()->getMode() !== 'disabled'): ?> <?php if ($this->permission()->allowDisplay('feature.Favorites')): ?> <?php /* Add to favorites; finc: keep Icon inside link - CK */ ?> - <a href="<?=$this->recordLink()->getActionUrl($this->driver, 'Save')?>" data-lightbox class="save-record result-link-label" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>" aria-label="<?=$this->transEsc('Add to favorites')?>"> + <a href="<?=$this->recordLink()->getActionUrl($this->driver, 'Save')?>?refreshOnClose=false" data-lightbox class="save-record result-link-label" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>" aria-label="<?=$this->transEsc('Add to favorites')?>"> <i class="fa fa-fw fa-star" aria-hidden="true"></i> <span><?=$this->transEsc('Add to favorites')?></span> </a><br/> <?php elseif ($block = $this->permission()->getAlternateContent('feature.Favorites')): ?> diff --git a/themes/finc/templates/RecordDriver/SolrAI/core.phtml b/themes/finc/templates/RecordDriver/SolrAI/core.phtml index 504fe19d7dc63a99975874b717c6c1db529101af..e6f20eacbfafa42e4e86a6979fed34b52fe56dcb 100644 --- a/themes/finc/templates/RecordDriver/SolrAI/core.phtml +++ b/themes/finc/templates/RecordDriver/SolrAI/core.phtml @@ -46,7 +46,7 @@ */ ?> <?php /* finc: add schema tags for title #13850 - VE */ ?> - <h1 property="name" lang=""><?= $this->escapeHtml(preg_replace('/(\s[\/\.:]\s*)*$/', '', $this->truncate($this->driver->getShortTitle() . ' ' . $this->driver->getSubtitle() . ' ' . $this->driver->getTitleSection(), 100))) ?></h1> + <h1 property="name" lang=""><?=$this->record($this->driver)->getTitleHtml()?></h1> <?php /* #18307 remove summary from core */ ?> diff --git a/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml index 0b8dc222334495369c4a119078df6840174a8a3f..5ca3d5c4e020227aaf00fd7f0b220944ae389778 100644 --- a/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml +++ b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml @@ -4,6 +4,7 @@ $coverDetails = $this->record($this->driver)->getCoverDetails('result-list', 'me $cover = $coverDetails['html']; $thumbnail = false; $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('result'); +$describedById = $driver->getSourceIdentifier() . '|' . $driver->getUniqueId(); if ($cover): ob_start(); ?> <div class="media-<?=$thumbnailAlignment?> <?=$this->escapeHtmlAttr($coverDetails['size'])?>" aria-hidden="true"> @@ -31,7 +32,7 @@ if ($cover): <div class="media-body"> <div class="result-body"> <div> - <a href="<?=$this->recordLink()->getUrl($this->driver)?>" class="title getFull" data-view="<?=$this->params->getOptions()->getListViewOption()?>" lang=""> + <a id="<?=$describedById?>" href="<?=$this->recordLink()->getUrl($this->driver)?>" class="title getFull" data-view="<?=$this->params->getOptions()->getListViewOption()?>" lang=""> <?=$this->record($this->driver)->getTitleHtml()?> </a> </div> @@ -104,30 +105,6 @@ if ($cover): <?php endif; ?> <?php endif; ?> - <?php - /* Display information on duplicate records if available */ - if ($dedupData = $this->driver->getDedupData()): ?> - <div class="dedupInformation"> - <?php - $i = 0; - foreach ($dedupData as $source => $current) { - if (++$i == 1) { - ?><span class="currentSource"><a href="<?=$this->recordLink()->getUrl($this->driver)?>"><?=$this->transEsc("source_$source", [], $source)?></a></span><?php - } else { - if ($i == 2) { - ?> <span class="otherSources">(<?=$this->transEsc('Other Sources')?>: <?php - } else { - ?>, <?php - } - ?><a href="<?=$this->recordLink()->getUrl($current['id'])?>"><?=$this->transEsc("source_$source", [], $source)?></a><?php - } - } - if ($i > 1) { - ?>)</span><?php - } ?> - </div> - <?php endif; ?> - <div class="callnumAndLocation ajax-availability hidden"> <?php if ($this->driver->supportsAjaxStatus()): ?> <strong class="hideIfDetailed"><?=$this->transEsc('Call Number')?>:</strong> @@ -170,10 +147,13 @@ if ($cover): <?php if (!is_array($urls)) $urls = []; if (!$this->driver->isCollection()): foreach ($urls as $current): ?> - <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>" class="fulltext" target="new"> - <i class="fa fa-external-link" aria-hidden="true"></i> - <?=($current['url'] == $current['desc']) ? $this->transEsc('Get full text') : $this->escapeHtml($current['desc'])?> - </a> + <?= $this->externalLink( + $this->escapeHtmlAttr($this->proxyUrl($current['url'])), + '<i class="fa fa-external-link" aria-hidden="true"></i>' + . ($current['url'] == $current['desc'] ? $this->transEsc('Get full text') : $this->escapeHtml($current['desc'])), + ['class' => 'fulltext'], + true + ) ?> <br/> <?php endforeach; ?> <?php endif; ?> @@ -219,7 +199,7 @@ if ($cover): <?php if ($this->userlist()->getMode() !== 'disabled'): ?> <?php /* if ($this->permission()->allowDisplay('feature.Favorites')): */ ?> <?php /* Add to favorites; finc: keep Icon inside link - CK */ ?> - <a href="<?=$this->recordLink()->getActionUrl($this->driver, 'Save')?>" data-lightbox class="save-record result-link-label" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>"> + <a href="<?=$this->recordLink()->getActionUrl($this->driver, 'Save')?>?refreshOnClose=false" data-lightbox class="save-record result-link-label" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>"> <i class="result-link-icon fa fa-fw fa-star" aria-hidden="true"></i> <?=$this->transEsc('Add to favorites')?> </a><br/> <?php elseif ($block = $this->permission()->getAlternateContent('feature.Favorites')): ?> diff --git a/themes/finc/templates/RecordDriver/SolrDico/data-license.phtml b/themes/finc/templates/RecordDriver/SolrDico/data-license.phtml new file mode 100644 index 0000000000000000000000000000000000000000..5cef1f3ac0db8b0faed390010fe4c9c930e2d2de --- /dev/null +++ b/themes/finc/templates/RecordDriver/SolrDico/data-license.phtml @@ -0,0 +1,17 @@ +<!-- finc: RecordDriver - SolrDico - data-license --> +<!-- in contrast to SolrMarc data-license.phtml, $data is an one-dimensional array and has an optional icon --> +<?php $label = $this->escapeHtml($data['code']) . " (" . $this->escapeHtml($data['text']) . ")"; ?> +<?php ob_start() ?> +<?=$label ?> +<?php if (isset($data['icon'])): ?> + <img class="collection-data collection-licence-image" src="<?=$data['icon']?>"/> +<?php endif; ?> +<?php $label = ob_get_contents(); + ob_end_clean(); +?> +<?php if (isset($data['url'])): ?> + <?= $this->externalLink($data['url'], $label) ?> +<?php else: ?> + <?= $label ?> +<?php endif;?> +<!-- finc: RecordDriver - SolrDico - data-license - END --> \ No newline at end of file diff --git a/themes/finc/templates/RecordDriver/SolrLido/core.phtml b/themes/finc/templates/RecordDriver/SolrLido/core.phtml index 5436e6000a8e689cb50c49edddc31049421d87b6..9a8cdb17928ba940f656a029fbdddbe1faf5c779 100644 --- a/themes/finc/templates/RecordDriver/SolrLido/core.phtml +++ b/themes/finc/templates/RecordDriver/SolrLido/core.phtml @@ -63,7 +63,7 @@ <?php /* finc: We want to get rid of trailing special chars in the title and limit its length to 100 chars; in finc: keep schema name tag here!! #13861 CK */ ?> - <h1 property="name" lang=""><?=$this->escapeHtml(preg_replace('/(\s[\/\.:]\s*)*$/', '', $this->truncate($this->driver->getShortTitle() . ' ' . $this->driver->getSubtitle() . ' ' . $this->driver->getTitleSection(), 100)))?></h1> + <h1 property="name" lang=""><?=$this->record($this->driver)->getTitleHtml()?></h1> <?php $summary = $this->driver->getSummary(); $summary = isset($summary[0]) ? $this->escapeHtml($summary[0]) : false; ?> diff --git a/themes/finc/templates/RecordDriver/SolrMarc/core.phtml b/themes/finc/templates/RecordDriver/SolrMarc/core.phtml index 89f4018e26abf6c4bccd8961fed1c7a92762290e..37fd560fc0623732c1132bc1770acf6d5f7118d9 100644 --- a/themes/finc/templates/RecordDriver/SolrMarc/core.phtml +++ b/themes/finc/templates/RecordDriver/SolrMarc/core.phtml @@ -46,7 +46,7 @@ */ ?> <?php /* finc: add schema tags for title #13850 - VE */ ?> - <h1 property="name" lang=""><?= $this->escapeHtml(preg_replace('/(\s[\/\.:]\s*)*$/', '', $this->truncate($this->driver->getShortTitle() . ' ' . $this->driver->getSubtitle() . ' ' . $this->driver->getTitleSection(), 100))) ?></h1> + <h1 property="name" lang=""><?=$this->record($this->driver)->getTitleHtml()?></h1> <?php /* #18307 remove summary from core */ ?> diff --git a/themes/finc/templates/RecordDriver/SolrMarc/data-license.phtml b/themes/finc/templates/RecordDriver/SolrMarc/data-license.phtml new file mode 100644 index 0000000000000000000000000000000000000000..7407ecf961246ea38be15eece3b0f5bb2323dfd0 --- /dev/null +++ b/themes/finc/templates/RecordDriver/SolrMarc/data-license.phtml @@ -0,0 +1,14 @@ +<!-- finc: RecordDriver - SolrMarc - data-licence --> +<?php if (is_array($data) && count($data)): ?> + <?php foreach ($data as $license) : ?> + <div> + <?php $text = ($license['code'] ?? '') . (isset($license['text']) ? " ({$license['text']})" : '') ?> + <?php if (isset($license['url'])): ?> + <?= $this->externalLink($license['url'], $text ?? $license['url']) ?> + <?php else: ?> + <?= $text ?> + <?php endif;?> + </div> + <?php endforeach; ?> +<?php endif; ?> +<!-- finc: RecordDriver - SolrMarc - data-licence - END --> \ No newline at end of file diff --git a/themes/finc/templates/RecordTab/acquisitionpda.phtml b/themes/finc/templates/RecordTab/acquisitionpda.phtml index 91296ba15fa006a007013d4545a6cbfcb675aa6f..e069017a899ee0ec5596fda3bb820e5e9df2dbb3 100644 --- a/themes/finc/templates/RecordTab/acquisitionpda.phtml +++ b/themes/finc/templates/RecordTab/acquisitionpda.phtml @@ -15,9 +15,13 @@ $controllerClass = 'controller:SolrMarcFincPDA'; <p class="alert alert-info"><?=$this->transEsc('PDA::pda_restriction_text')?></p> <div class="btn-group"> <?php /* Leave title in here - it is used for the tooltip! - CK */ ?> - <a class="btn btn-primary" data-toggle="tooltip" title="<?=$this->transEsc('PDA::pda_open_new_window')?>" href="<?=$this->interlibraryloan()->getSwbLink($this->driver)?>" target="_blank"> - <?=$this->transEsc('PDA::pda_tab_interlibrary_button')?> - </a> + <?= $this->externalLink( + $this->interlibraryloan()->getSwbLink($this->driver), + $this->translate('PDA::pda_tab_interlibrary_button'), + [ 'class' => 'btn btn-primary', + 'data-toggle' => 'tooltip', + 'title' => $this->translate('PDA::pda_open_new_window')] + ) ?> <a class="btn btn-primary pda-button <?=$controllerClass?>" data-lightbox href="<?=$this->url('record-pda', array('id' => $id))?>" rel="nofollow"> <?=$this->transEsc('PDA::pda_tab_order_button')?> </a> diff --git a/themes/finc/templates/RecordTab/holdingsils.phtml b/themes/finc/templates/RecordTab/holdingsils.phtml index d929e6b59b9855a214f6df8fa0c0852f307a6d1e..392dd55a860ef27148f0782b7d3ea375283f8c8c 100644 --- a/themes/finc/templates/RecordTab/holdingsils.phtml +++ b/themes/finc/templates/RecordTab/holdingsils.phtml @@ -65,7 +65,7 @@ if (!empty($holdingTitleHold)): ?> <h2><?=$this->transEsc("Internet")?></h2> <?php if (!empty($urls)): ?> <?php foreach ($urls as $current): ?> - <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>"><?=$this->escapeHtml($current['desc'])?></a><br/> + <?= $this->externalLink($this->escapeHtmlAttr($this->proxyUrl($current['url'])), $current['desc']) ?><br/> <?php endforeach; ?> <?php endif; ?> <?php /* finc-specific snippet - #9274 - replaces if ($openUrlActive): ... - CK */ ?> @@ -75,7 +75,7 @@ if (!empty($holdingTitleHold)): ?> if (!empty($fallbackUrls)): ?> <span id="urlsHideable" style="display: none"> <?php foreach ($fallbackUrls as $current): ?> - <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>" target="_blank"><?=$this->escapeHtml($current['desc'] ?? $current['url'])?></a><br/> + <?= $this->externalLink($this->escapeHtmlAttr($this->proxyUrl($current['url'])), $current['desc'] ?? $current['url']) ?><br/> <?php endforeach; ?> </span> <?php endif; ?> @@ -93,7 +93,7 @@ if (!empty($holdingTitleHold)): ?> <h2> <?php $locationText = $this->transEsc('location_' . $holding['location'], [], $holding['location']); ?> <?php if (isset($holding['locationhref']) && $holding['locationhref']): ?> - <a href="<?=$holding['locationhref']?>" target="_blank"><?=$locationText?></a> + <?= $this->externalLink($holding['locationhref'], $locationText) ?> <?php else: ?> <?=$locationText?> <?php endif; ?> @@ -179,7 +179,7 @@ if (!empty($holdingTitleHold)): ?> <h2><?=$this->transEsc("external_access")?></h2> <?php if (!empty($extUrls)): ?> <?php foreach ($extUrls as $current): ?> - <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>"><?=$this->translate($current['desc'])?></a> + <?=$this->externalLink($this->escapeHtmlAttr($this->proxyUrl($current['url'])), $this->translate($current['desc']))?> <br/> <?php endforeach; ?> <?php endif; ?> diff --git a/themes/finc/templates/RecordTab/toc.phtml b/themes/finc/templates/RecordTab/toc.phtml new file mode 100644 index 0000000000000000000000000000000000000000..eb89dc704ee6e9e182599bac1de1076753b76bf9 --- /dev/null +++ b/themes/finc/templates/RecordTab/toc.phtml @@ -0,0 +1,16 @@ +<!-- finc: RecordTab - toc --> +<?php + // Set page title. + $this->headTitle($this->translate('Table of Contents') . ': ' . $this->driver->getBreadcrumb()); + $formatter = $this->recordDataFormatter(); + $mainFields = $formatter->getData($this->driver, $formatter->getDefaults('toc')); +?> +<?php if (!empty($mainFields)): ?> + <?php foreach ($mainFields as $current): ?> + <strong><?=$this->transEsc($current['label'])?>: </strong> + <?=$current['value']?> + <?php endforeach; ?> +<?php else: ?> + <?=$this->transEsc('Table of Contents unavailable')?>. +<?php endif; ?> +<!-- finc: RecordTab - toc - END --> \ No newline at end of file diff --git a/themes/finc/templates/admin/i18n/home.phtml b/themes/finc/templates/admin/i18n/home.phtml index 1742553be46f6279e424f9c0e88134fbd2287e4d..0e634d2a26596a0f79e5e655361d82c0636d5a54 100644 --- a/themes/finc/templates/admin/i18n/home.phtml +++ b/themes/finc/templates/admin/i18n/home.phtml @@ -1,7 +1,7 @@ <!-- finc: admin - i18n - home --> <?php $this->headTitle($this->translate('admin_i18n_menu_entry')); ?> -<div class="<?= ($this->layoutClass('mainbody') . (!$this->config()->get('config')->Site->admin_enabled ? ' solo' : '')) ?>"> +<div class="<?= ($this->layoutClass('mainbody') . (!$this->config()->get('config')->Site->admin_enabled ? ' solo' : '')) ?>" style="width: 100%"> <h1><?= $this->transEsc('admin_i18n_menu_entry') ?></h1> <?= $this->flashmessages() ?> <div class="panel panel-info"> @@ -76,12 +76,6 @@ </table> </div> -<?php if ($this->config()->get('config')->Site->admin_enabled): ?> - <div class="<?= $this->layoutClass('sidebar') ?>"> - <?= $this->render("admin/menu.phtml") ?> - </div> -<?php endif; ?> - <?php $script = <<<JS $(document).ready(function () { diff --git a/themes/finc/templates/ajax/resolverLinks.phtml b/themes/finc/templates/ajax/resolverLinks.phtml index 9b46024cabfd134f762343dc09fb45b715ec86fe..4b2e0a9ed2b4ee8050d643752941eb0adb76f175 100644 --- a/themes/finc/templates/ajax/resolverLinks.phtml +++ b/themes/finc/templates/ajax/resolverLinks.phtml @@ -20,12 +20,24 @@ <span class="last"></span> </div> <?php /* finc-specific change #7986 - END */ ?> - <a href="<?=$this->escapeHtmlAttr($link['href'])?>" title="<?=isset($link['service_type'])?$this->escapeHtmlAttr($link['service_type']):''?>"<?=!empty($link['access'])?' class="access-'.$link['access'].'"':''?>><?=isset($link['title'])?$this->escapeHtml($link['title']):''?></a> <br /> + <?= $this->externalLink( + $this->escapeHtmlAttr($link['href']), + $link['title'] ?? '', + [ + 'title' => $link['service_type'] ?? '', + 'class' => !empty($link['access']) ? 'access-' . $link['access'] : '' + ] + ) ?> <br /> <?php /* finc-specific change #5334 - CK */ ?> - <small><?=isset($link['coverage'])?$this->escapeHtml($link['coverage']):''?><?=isset($link['coverageHref'])?' <a href="'.$link['coverageHref'].'" target="_blank">'.$this->translate('Readme').'</a>':''?></small> + <small> + <?= isset($link['coverage']) ? $this->escapeHtml($link['coverage']) : '' ?> + <?= isset($link['coverageHref']) + ? $this->externalLink($link['coverageHref'], $this->translate('Readme')) + : '' ?> + </small> <?php /* finc-specific change #5334 - END */ ?> <?php else: ?> - <?=isset($link['title'])?$this->escapeHtml($link['title']):''?> <?=isset($link['coverage'])?$this->escapeHtml($link['coverage']):''?> + <?=isset($link['title'])?$this->escapeHtml($link['title']):''?> <?=isset($link['coverage'])?$this->transEsc($link['coverage']):''?> <?php endif; ?> </li> <?php endforeach; ?> diff --git a/themes/finc/templates/ajax/status-full.phtml b/themes/finc/templates/ajax/status-full.phtml index 303067afd3f54d49ba871af84d7028fc758dcb9f..f48411b6a7613839ee6b963ee2510646bc2053be 100644 --- a/themes/finc/templates/ajax/status-full.phtml +++ b/themes/finc/templates/ajax/status-full.phtml @@ -12,7 +12,7 @@ <td data-title="<?= $this->transEsc('Location') ?>:" class="fullLocation"> <?php $locationText = $this->transEsc('location_' . $item['location'], [], $item['location']); ?> <?php if (isset($item['locationhref']) && $item['locationhref']): ?> - <a href="<?=$item['locationhref']?>" target="_blank"><?=$locationText?></a> + <?= $this->externalLink($item['locationhref'], $locationText) ?> <?php else: ?> <?=$locationText?> <?php endif; ?> diff --git a/themes/finc/templates/amsl/sources-list.phtml b/themes/finc/templates/amsl/sources-list.phtml index 371f906684de2b40f1af54bc0857935c9662358e..8970d0d041d4de5d3bbc0838e7a59a97c667e406 100644 --- a/themes/finc/templates/amsl/sources-list.phtml +++ b/themes/finc/templates/amsl/sources-list.phtml @@ -49,9 +49,11 @@ $this->layout()->breadcrumbs .= '</li> <li class="active">' . $this->transEsc('L <?php foreach ($source as $sub_label => $collection): ?> <li> <?php if (!empty($collection['href'])): ?> - <a title="<?=$this->transEsc("Search For")?> <?=$sub_label?>" href='<?=$collection["href"]?>' target="_blank"> - <?=$sub_label?> - </a> + <?= $this->externalLink( + $collection["href"], + $sub_label, + ['title' => "{$this->transEsc("Search For")} {$sub_label}"] + ) ?> <?php else: ?> <div tabindex="0" aria-label="<?=$this->transEsc("Source Title")?>"> <?=$sub_label?> @@ -77,8 +79,12 @@ $this->layout()->breadcrumbs .= '</li> <li class="active">' . $this->transEsc('L <span> <?=$this->transEsc('support_by_dfg');?> </span> - <a href='http://www.dfg.de' target='_blank'> - <img src='<?=$this->imageLink('dfg_logo_text.png')?>' alt='Deutsche Forschungsgemeinschaft, DFG'> + <?= $this->externalLink( + 'http://www.dfg.de', + "<img src='{$this->imageLink('dfg_logo_text.png')}' alt='Deutsche Forschungsgemeinschaft, DFG'>", + [], + true + ) ?> </a> </div> </div> diff --git a/themes/finc/templates/documentdeliveryservice/form.phtml b/themes/finc/templates/documentdeliveryservice/form.phtml deleted file mode 100644 index c724f5e2ef1f34f051d9680157cf0242cedf9759..0000000000000000000000000000000000000000 --- a/themes/finc/templates/documentdeliveryservice/form.phtml +++ /dev/null @@ -1,302 +0,0 @@ -<!-- finc - documentdeliveryservice - form --> -<div class="<?=$this->layoutClass('mainbody')?>"> - <h1><?=$this->transEsc('DDS::dds_form_headline')?></h1> - <?=$this->flashmessages()?> - <form method="post" action="<?=$this->url('dds-email')?>" name="docdelisForm"> - <?php if ($this->department): ?> - <input type="hidden" name="subito[hdepartment]" value="<?=$this->department?>"/> - <?php endif; ?> - <legend><?=$this->transEsc("DDS::dds_form_delivery_data")?>:</legend> - <!-- name --> - <div class="form-group"> - <label for="sname" class="control-label"><?=$this->transEsc("Name")?>:*</label> - <?php if (isset($this->error->username)): ?> - <div class="error-field"> - <?=$this->error->username;?> - </div> - <?php endif; ?> - - <input type="text" id="sname" name="subito[username]" class="form-control" <?=(!empty($this->username) ? 'value="' . $this->username . '"' : '')?>/> - - </div> - <!-- phone --> - <div class="form-group"> - <label class="inline control-label" for="sphone"><?=$this->transEsc("DDS::form_field_phone")?>:</label> - <?php if (isset($this->error->phone)): ?> - <div class="error-field"> - <?=$this->error->phone;?> - </div> - <?php endif; ?> - <input type="text" id="sphone" name="subito[phone]" class="form-control" <?=(!empty($this->phone) ? 'value="' . $this->phone . '"' : '')?>/> - </div> - <!-- email --> - <div class="form-group"> - <label class="inline control-label" for="semail"><?=$this->transEsc("DDS::form_field_email")?>:*</label> - <?php if (isset($this->error->email)): ?> - <div class="error-field"> - <?=$this->error->email;?> - </div> - <?php endif; ?> - <input type="text" id="semail" name="subito[email]" class="form-control" <?=(!empty($this->email) ? 'value="' . $this->email . '"' : '')?>/> - </div> - <!-- user id --> - <div class="form-group"> - <label class="inline control-label" for="suserid"><?=$this->transEsc("DDS::form_field_library_id")?>:*</label> - <?php if (isset($this->error->userid)): ?> - <div class="error-field"> - <?=$this->error->userid;?> - </div> - <?php endif; ?> - <input type="text" id="suserid" name="subito[userid]" class="form-control" <?=(!empty($this->userid) ? 'value="' . $this->userid . '"' : '')?>/> - </div> - <!-- division --> - <div class="form-group"> - <label class="inline control-label" for="sdivision"><?=$this->transEsc("DDS::form_field_division")?>:*</label> - <?php if (isset($this->error->division)): ?> - <div class="error-field"> - <?=$this->error->division;?> - </div> - <?php endif; ?> - <select id="sdivision" name="subito[division]" size="1" class="form-control"/> - <option value=""></option> - <?php foreach ($this->divisions as $cat => $division): ?> - <option value="<?=$cat?>" <?=($this->division == $cat) ? 'selected="selected"' : ''?>><?=$division?></option> - <?php endforeach; ?> - </select> - </div> - <!-- department --> - <div class="form-group input-sdepartment"> - <label class="inline control-label" for="sdepartment"><?=$this->transEsc("DDS::form_field_department")?>:*</label> - <?php if (isset($this->error->department)): ?> - <div class="error-field"> - <?=$this->error->department;?> - </div> - <?php endif; ?> - <select id="sdepartment" name="subito[department]" size="1" class="form-control"/> - <option value=""></option> - </select> - </div> - <!-- department medicine --> - <div class="form-group input-department"> - <label class="inline control-label" for="smdepartment"><?=$this->transEsc("DDS::form_field_department")?>:*</label> - <input type="text" id="smdepartment" name="subito[inputdepartment]" class="form-control" <?=(!empty($this->inputdepartment) ? 'value="' . $this->inputdepartment . '"' : '')?>/> - </div> - - <legend><?=$this->transEsc("DDS::dds_form_details_ordered_title")?>:</legend> - <!-- author --> - <div class="form-group"> - <label class="inline control-label" for="sauthor"><?=$this->transEsc("DDS::form_field_author")?>:*</label> - <?php if (isset($this->error->author)): ?> - <div class="error-field"> - <?=$this->error->author;?> - </div> - <?php endif; ?> - <input type="text" id="sauthor" name="subito[author]" class="form-control" <?=(!empty($this->author) ? 'value="' . $this->author . '"' : '')?>/> - </div> - <!-- title of issue --> - <div class="form-group"> - <label class="inline control-label" for="sarticle"><?=$this->transEsc("DDS::form_field_title")?>:</label> - <?php if (isset($this->error->article)): ?> - <div class="error-field"> - <?=$this->error->article;?> - </div> - <?php endif; ?> - <input type="text" id="sarticle" name="subito[article]" class="form-control" <?=(!empty($this->article) ? 'value="' . $this->article . '"' : '')?>/> - </div> - <!-- title of journal --> - <div class="form-group"> - <label class="inline control-label" for="sjournal"><?=$this->transEsc("DDS::form_field_journal")?>:*</label> - <?php if (isset($this->error->journal)): ?> - <div class="error-field"> - <?=$this->error->journal;?> - </div> - <?php endif; ?> - <input type="text" id="sjournal" name="subito[journal]" class="form-control" <?=(!empty($this->journal) ? 'value="' . $this->journal . '"' : '')?>/> - </div> - <!-- issn --> - <div class="form-group"> - <label class="inline control-label" for="sjournal"><?=$this->transEsc("ISSN")?>:</label> - <?php if (isset($this->error->issn)): ?> - <div class="error-field"> - <?=$this->error->issn;?> - </div> - <?php endif; ?> - <input type="text" id="sissn" name="subito[issn]" class="form-control" <?=(!empty($this->issn) ? 'value="' . $this->issn . '"' : '')?>/> - </div> - <!-- publish date --> - <div class="form-group"> - <label class="inline control-label" for="spublishdate"><?=$this->transEsc("DDS::form_field_publishing_date")?>:*</label> - <?php if (isset($this->error->publishdate)): ?> - <div class="error-field"> - <?=$this->error->publishdate;?> - </div> - <?php endif; ?> - <input type="text" id="spublishdate" name="subito[publishdate]" class="form-control" <?=(!empty($this->publishdate) ? 'value="' . $this->publishdate . '"' : '')?>/> - </div> - <!-- journal number --> - <div class="form-group"> - <label class="inline control-label" for="snumber"><?=$this->transEsc("DDS::form_field_volume")?>:*</label> - <?php if (isset($this->error->number)): ?> - <div class="error-field"> - <?=$this->error->number;?> - </div> - <?php endif; ?> - <input type="text" id="snumber" name="subito[number]" class="form-control" <?=(!empty($this->number) ? 'value="' . $this->number . '"' : '')?>/> - </div> - <!-- pages --> - <div class="form-group"> - <label class="inline control-label" for="spages"><?=$this->transEsc("DDS::form_field_pages")?>:*</label> - <?php if (isset($this->error->pages)): ?> - <div class="error-field"> - <?=$this->error->pages;?> - </div> - <?php endif; ?> - <input type="text" id="spages" name="subito[pages]" class="form-control" <?=(!empty($this->pages) ? 'value="' . $this->pages . '"' : '')?>/> - </div> - <!-- remarks --> - <div class="form-group"> - <label class="inline control-label" for="sremarks"><?=$this->transEsc("DDS::form_fields_remarks")?>:</label> - <?php if (isset($this->error->remarks)): ?> - <div class="error-field"> - <?=$this->error->remarks;?> - </div> - <?php endif; ?> - <textarea id="sremarks" name="subito[remarks]" class="form-control"><?=(!empty($this->remarks) ? $this->remarks : '')?></textarea> - </div> - <input role="button" type="submit" class="btn btn-primary" value="<?=$this->transEsc("DDS::form_button_submit")?>"/> - - </form> - <div class="subito-pg margin-t"> - <?=$this->transEsc("DDS::dds_text_questions")?> - <a href="mailto:info@ub.uni-leipzig.de?subject=<?=$this->transEsc("Dokumentenlieferdienst")?>">info@ub.uni-leipzig.de</a> - </div> - - <div class="input-department subito-pg"> - <div><?=$this->transEsc("Bei eventuellen Rückfragen wenden Sie sich bitte an die Abteilung Fernleihe der Medizin")?>:</div> - <div><?=$this->transEsc("Phone")?>: +49 (0)341 - 97 14014</div> - <div><?=$this->transEsc("Email")?>: <a href="mailto:zbmed.fernleihe@medizin.uni-leipzig.de?subject=<?=$this->transEsc("Dokumentenlieferdienst")?>">zbmed.fernleihe@medizin.uni-leipzig.de</a> - </div> - </div> - <div class="subito-pg"> - <p class="required"> - * <?=$this->transEsc("DDS::dds_text_mandatory_fields")?></p> - </div> -</div> - -<?php -$this->inlineScript()->captureStart(); -echo <<<JS - $(document).ready(function(){ - // if department already selected and post request failed then - // rebuild select menu - if ( $('input:hidden[name="subito\\[hdepartment\\]"]').val() != 'undefined') { - department.addDepartmentSelect( - $('input[name="subito\\[hdepartment\\]"]').val(), - $('select[name="subito\\[division\\]"]').val() - ); - } - $('select[name="subito\\[division\\]"]').change(function() { - department.init($(this).val()); - }); - $('.form-group input').focus(function() { - $(this).parent().prev('.error-field').hide(200); - }); - $('.form-group select').focus(function() { - $(this).parent().prev('.error-field').hide(200); - }); - /*if ( $('#loginOptions a.login').length ) { - var loginUrl = { - followup: true, - followupModule: 'Subito', - followupAction: 'Subito' - }; - $('#loginOptions a.login').attr('href', function ( index, value ) { - return value + '?' + $.param( loginUrl ); - }); - // console.log($('#loginOptions a.login').attr('href')); - }*/ - }); -/** -* Show & hide of subito form elements regarding select menu status. -* Add options for department select menu -* -* @author Frank Morgner<morgnerf@ub.uni-leipzig.de> -* @see https://katalog.ub.uni-leipzig.de/Subito/Subito -**/ - -var department = { - init: function ( divisionid ) { - addOptions = false; - var elements = []; - if ( divisionid == '15') { - elements = { - "department":"show", - "costcentre":"show", - "sdepartment":"hide" - } - } else if (divisionid == '') { - elements = { - "department":"hide", - "costcentre":"hide", - "sdepartment":"hide" - } - } else { - elements = { - "department":"hide", - "costcentre":"hide", - "sdepartment":"show" - } - var addOptions = true; - } - department.showHideElements(elements); - if (addOptions == true) { - department.addDepartmentOptions( divisionid ); - } - }, - addDepartmentOptions: function ( divisionid ) { - $('select[name="subito\\[department\\]"]').empty(); - var departments = $this->departments; - department.addOptionElement('',''); - $.each(departments[divisionid], function (index, value) { - department.addOptionElement(value,index); - }); - }, - addDepartmentSelect: function (departmentid, divisionid) { - department.init ( divisionid ); - department.addSelectElement ( departmentid ); - }, - addOptionElement: function ( text, value ) { - jQuery('<option/>', { - text: department.htmlEntitiesDecode(text), - value: value - }).appendTo('select[name="subito\\[department\\]"]'); - }, - addSelectElement: function ( departmentid ) { - $('select[name="subito\\[department\\]"] option').filter(function () { - //console.log($(this).val() + ' == ' + departmentid); - return $(this).val() == departmentid; - }).prop('selected', true); - }, - htmlEntitiesDecode: function ( value ) { - if (value) { - return jQuery('<div/>').html(value).text(); - } - return value; - }, - showHideElements: function ( elements ) { - $.each(elements, function (index, value) { - if (value == 'show') { - $('.input-' + index).show(200); - } - if (value == 'hide') { - $('.input-' + index).hide(200); - } - }); - } -} -JS; -$this->inlineScript()->captureEnd(); - -echo $this->inlineScript(); -?> -<!-- finc - documentdeliveryservice - form - END --> diff --git a/themes/finc/templates/documentdeliveryservice/home.phtml b/themes/finc/templates/documentdeliveryservice/home.phtml index bf92244c324628580b2de650cfcbc63263264e5a..db283942371cf432ffc274e3b675f55af9d3ed8c 100644 --- a/themes/finc/templates/documentdeliveryservice/home.phtml +++ b/themes/finc/templates/documentdeliveryservice/home.phtml @@ -3,4 +3,4 @@ $this->headTitle($this->translate('Delivery service for documents')); ?> -<?=($this->loadForm ? $this->render('documentdeliveryservice/form.phtml') : $this->flashmessages());?> \ No newline at end of file +<?=($this->loadForm ? $this->render('documentdeliveryservice/zform.phtml') : $this->flashmessages());?> \ No newline at end of file diff --git a/themes/finc/templates/documentdeliveryservice/zform.phtml b/themes/finc/templates/documentdeliveryservice/zform.phtml new file mode 100644 index 0000000000000000000000000000000000000000..4f9481ef1576f6cae903b6ae234e8eecb731d49a --- /dev/null +++ b/themes/finc/templates/documentdeliveryservice/zform.phtml @@ -0,0 +1,390 @@ +<!-- finc - documentdeliveryservice - form --> +<?php +/** + * Copyright (C) 2021 Leipzig University Library + * + * 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. + * + * @author Gregor Gawol <gawol@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU GPLv2 + */ + +use Zend\Form\Element; +use Zend\Form\Element\Submit; +use Zend\Form\Form; +use Zend\Form\View\Helper\FormElementErrors; +use Zend\Form\View\Helper\FormLabel; +use Zend\Form\View\Helper\FormSelect; +use Zend\Form\View\Helper\FormSubmit; + +/** @var Form $form */ +/** @var FormLabel $formLabel */ +/** @var FormSelect $formSelect */ +/** @var FormSubmit $formSubmit */ +/** @var FormElementErrors $formElementErrors */ +$form = $this->form; +$formLabel = $this->formLabel(); +$formSelect = $this->formSelect(); +$formSubmit = $this->formSubmit(); +$formElementErrors = $this->formElementErrors(); +$formLabel->setTranslatorTextDomain('DDS'); +$formSelect->setTranslatorTextDomain('DDS'); +$formSubmit->setTranslatorTextDomain('DDS'); +$formElementErrors->setTranslatorTextDomain('DDS'); +?> +<div class="<?=$this->layoutClass('mainbody')?>"> + <h1><?=$this->transEsc('DDS::dds_form_headline')?></h1> + <?=$this->flashmessages()?> + <?=$this->form()->openTag($form)?> + <?php if ($this->department): ?> + <input type="hidden" name="subito[hdepartment]" value="<?=$this->department?>"/> + <?php endif; ?> + <? /* name */ ?> + <?php + /** @var Element\Text $elemName */ + $elemName = $form->get('username'); + $elemName->setLabelAttributes(['class' => 'control-label']); + $elemName->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemName)?> + <?=$this->formElement($elemName)?> + <?=$this->formElementErrors($elemName)?> + </div> + + <? /* phone */ ?> + <?php + /** @var Element\Text $elemPhone */ + $elemPhone = $form->get('phone'); + $elemPhone->setLabelAttributes(['class' => 'control-label']); + $elemPhone->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemPhone)?> + <?=$this->formElement($elemPhone)?> + </div> + + <? /* email */ ?> + <?php + /** @var Element\Email $elemEmail */ + $elemEmail = $form->get('email'); + $elemEmail->setLabelAttributes(['class' => 'control-label']); + $elemEmail->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemEmail)?> + <?=$this->formElement($elemEmail)?> + <?=$this->formElementErrors($elemEmail)?> + </div> + + <? /* userid */ ?> + <?php + /** @var Element\Text $elemUserid */ + $elemUserid = $form->get('userid'); + $elemUserid->setLabelAttributes(['class' => 'control-label']); + $elemUserid->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemUserid)?> + <?=$this->formElement($elemUserid)?> + <?=$this->formElementErrors($elemUserid)?> + </div> + + <? /* division */ ?> + <?php + /** @var Element\Select $elemDivision */ + $elemDivision = $form->get('division'); + $elemDivision->setLabelAttributes(['class' => 'inline control-label']); + $elemDivision->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemDivision)?> + <?=$this->formSelect($elemDivision)?> + <?=$this->formElementErrors($elemDivision)?> + </div> + + <? /* department */ ?> + <?php + /** @var Element\Select $elemDepartment */ + $elemDepartment = $form->get('department'); + $elemDepartment->setLabelAttributes(['class' => 'inline control-label']); + $elemDepartment->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group input-sdepartment"> + <?=$this->formLabel($elemDepartment)?> + <?=$this->formSelect($elemDepartment)?> + <?=$this->formElementErrors($elemDepartment)?> + </div> + + <? /* department medicine */ ?> + <?php + /** @var Element\Text $elemMdepartment */ + $elemMdepartment = $form->get('inputdepartment'); + $elemMdepartment->setLabelAttributes(['class' => 'control-label']); + $elemMdepartment->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group input-department"> + <?=$this->formLabel($elemMdepartment)?> + <?=$this->formElement($elemMdepartment)?> + <?=$this->formElementErrors($elemMdepartment)?> + </div> + + <? /* author */ ?> + <?php + /** @var Element\Text $elemAuthor */ + $elemAuthor = $form->get('author'); + $elemAuthor->setLabelAttributes(['class' => 'control-label']); + $elemAuthor->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemAuthor)?> + <?=$this->formElement($elemAuthor)?> + <?=$this->formElementErrors($elemAuthor)?> + </div> + + <? /* title of issue */ ?> + <?php + /** @var Element\Text $elemArticle */ + $elemArticle = $form->get('article'); + $elemArticle->setLabelAttributes(['class' => 'control-label']); + $elemArticle->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemArticle)?> + <?=$this->formElement($elemArticle)?> + <?=$this->formElementErrors($elemArticle)?> + </div> + + <? /* title of journal */ ?> + <?php + /** @var Element\Text $elemJournal */ + $elemJournal = $form->get('journal'); + $elemJournal->setLabelAttributes(['class' => 'control-label']); + $elemJournal->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemJournal)?> + <?=$this->formElement($elemJournal)?> + <?=$this->formElementErrors($elemJournal)?> + </div> + + <? /* issn */ ?> + <?php + /** @var Element\Text $elemIssn */ + $elemIssn = $form->get('issn'); + $elemIssn->setLabelAttributes(['class' => 'control-label']); + $elemIssn->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemIssn)?> + <?=$this->formElement($elemIssn)?> + <?=$this->formElementErrors($elemIssn)?> + </div> + + <? /* publish date */ ?> + <?php + /** @var Element\Text $elemPublishdate */ + $elemPublishdate = $form->get('publishdate'); + $elemPublishdate->setLabelAttributes(['class' => 'control-label']); + $elemPublishdate->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemPublishdate)?> + <?=$this->formElement($elemPublishdate)?> + <?=$this->formElementErrors($elemPublishdate)?> + </div> + + <? /* journal number */ ?> + <?php + /** @var Element\Text $elemNumber */ + $elemNumber = $form->get('number'); + $elemNumber->setLabelAttributes(['class' => 'control-label']); + $elemNumber->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemNumber)?> + <?=$this->formElement($elemNumber)?> + <?=$this->formElementErrors($elemNumber)?> + </div> + + <? /* pages */ ?> + <?php + /** @var Element\Text $elemPages */ + $elemPages = $form->get('pages'); + $elemPages->setLabelAttributes(['class' => 'control-label']); + $elemPages->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemPages)?> + <?=$this->formElement($elemPages)?> + <?=$this->formElementErrors($elemPages)?> + </div> + + <? /* remarks */ ?> + <?php + /** @var Element\Textarea $elemRemarks */ + $elemRemarks = $form->get('remarks'); + $elemRemarks->setLabelAttributes(['class' => 'control-label']); + $elemRemarks->setAttributes(['class' => 'form-control']); + ?> + <div class="form-group"> + <?=$this->formLabel($elemRemarks)?> + <?=$this->formElement($elemRemarks)?> + <?=$this->formElementErrors($elemRemarks)?> + </div> + + <? /* submit button */ ?> + <?php + /** @var Submit $elemSubmit */ + $elemSubmit = $form->get('submit'); + $elemSubmit->setAttributes(['class' => 'btn btn-primary']); + ?> + + <div class="form-group"><?= $this->formSubmit($elemSubmit) ?></div> + <?= $this->form()->closeTag($form)?> + + <div class="subito-pg margin-t"> + <?=$this->transEsc("DDS::dds_text_questions")?> + <a href="mailto:info@ub.uni-leipzig.de?subject=<?=$this->transEsc("Dokumentenlieferdienst")?>">info@ub.uni-leipzig.de</a> + </div> + + <div class="input-department subito-pg"> + <div><?=$this->transEsc("Bei eventuellen Rückfragen wenden Sie sich bitte an die Abteilung Fernleihe der Medizin")?>:</div> + <div><?=$this->transEsc("Phone")?>: +49 (0)341 - 97 14014</div> + <div><?=$this->transEsc("Email")?>: <a href="mailto:zbmed.fernleihe@medizin.uni-leipzig.de?subject=<?=$this->transEsc("Dokumentenlieferdienst")?>">zbmed.fernleihe@medizin.uni-leipzig.de</a> + </div> + </div> + <div class="subito-pg"> + <p class="required"> + * <?=$this->transEsc("DDS::dds_text_mandatory_fields")?></p> + </div> +</div> + +<?php +$this->inlineScript()->captureStart(); +echo <<<JS + $(document).ready(function(){ + // if department already selected and post request failed then + // rebuild select menu + if ( $('input:hidden[name="department"]').val() != 'undefined') { + department.addDepartmentSelect( + $('input[name="department"]').val(), + $('select[name="division"]').val() + ); + } + $('select[name="division"]').change(function() { + department.init($(this).val()); + }); + // $('.form-group input').focus(function() { + // $(this).parent().prev('.error-field').hide(200); + // }); + // $('.form-group select').focus(function() { + // $(this).parent().prev('.error-field').hide(200); + // }); + /*if ( $('#loginOptions a.login').length ) { + var loginUrl = { + followup: true, + followupModule: 'Subito', + followupAction: 'Subito' + }; + $('#loginOptions a.login').attr('href', function ( index, value ) { + return value + '?' + $.param( loginUrl ); + }); + // console.log($('#loginOptions a.login').attr('href')); + }*/ + }); +/** +* Show & hide of subito form elements regarding select menu status. +* Add options for department select menu +* +* @author Frank Morgner<morgnerf@ub.uni-leipzig.de> +* @see https://katalog.ub.uni-leipzig.de/Subito/Subito +**/ + +var department = { + init: function ( divisionid ) { + addOptions = false; + var elements = []; + if ( divisionid == '15') { + elements = { + "department":"show", + "costcentre":"show", + "sdepartment":"hide" + } + } else if (divisionid == '') { + elements = { + "department":"hide", + "costcentre":"hide", + "sdepartment":"hide" + } + } else { + elements = { + "department":"hide", + "costcentre":"hide", + "sdepartment":"show" + } + var addOptions = true; + } + department.showHideElements(elements); + if (addOptions == true) { + department.addDepartmentOptions( divisionid ); + } + }, + addDepartmentOptions: function ( divisionid ) { + $('select[name="department"]').empty(); + var departments = $this->departments; + department.addOptionElement('',''); + $.each(departments[divisionid], function (index, value) { + department.addOptionElement(value,index); + }); + }, + addDepartmentSelect: function (departmentid, divisionid) { + department.init ( divisionid ); + department.addSelectElement ( departmentid ); + }, + addOptionElement: function ( text, value ) { + jQuery('<option/>', { + text: department.htmlEntitiesDecode(text), + value: value + }).appendTo('select[name="department"]'); + }, + addSelectElement: function ( departmentid ) { + $('select[name="department"] option').filter(function () { + //console.log($(this).val() + ' == ' + departmentid); + return $(this).val() == departmentid; + }).prop('selected', true); + }, + htmlEntitiesDecode: function ( value ) { + if (value) { + return jQuery('<div/>').html(value).text(); + } + return value; + }, + showHideElements: function ( elements ) { + $.each(elements, function (index, value) { + if (value == 'show') { + $('.input-' + index).show(200); + } + if (value == 'hide') { + $('.input-' + index).hide(200); + } + }); + } +} +JS; +$this->inlineScript()->captureEnd(); + +echo $this->inlineScript(); +?> +<!-- finc - documentdeliveryservice - form - END --> diff --git a/themes/finc/templates/footer.phtml b/themes/finc/templates/footer.phtml index e0e0eae30b028c7913c59d682515273782a6d5ad..719d70099b343d088d6db736236e067b6fa92110 100644 --- a/themes/finc/templates/footer.phtml +++ b/themes/finc/templates/footer.phtml @@ -37,8 +37,8 @@ <span> <?= $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("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']) ?> + <?= $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')], 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')], true) ?> </div> </div> </footer> diff --git a/themes/finc/templates/header.phtml b/themes/finc/templates/header.phtml index 7a37482bbcc7ac0c29756766a026eb3335cba979..67b8f3acd01568715ac098fdb5c6741fe52f82d9 100644 --- a/themes/finc/templates/header.phtml +++ b/themes/finc/templates/header.phtml @@ -38,7 +38,7 @@ if ($cart->isActive()): ?> <li id="cartSummary"> <a id="cartItems" class="btn" data-lightbox title="<?=$this->transEsc('View Book Bag')?>" href="<?=$this->url('cart-home')?>"> - <i class="fa fa-clipboard" aria-hidden="true"></i> <strong><?=count($cart->getItems())?></strong> <span class="cart-label"><?=$this->transEsc('items')?></span> + <i class="fa fa-clipboard" aria-hidden="true"></i> <span role="status"><span class="sr-only"><?=$this->transEsc('Book Bag')?>:</span> <strong><?=count($cart->getItems())?></strong> <span class="cart-label"><?=$this->transEsc('items')?></span></span> <span class="sr-only full<?=!$cart->isFull() ? ' hidden' : ''?>">(<?=$this->transEsc('bookbag_full')?>)</span> </a> </li> @@ -109,7 +109,7 @@ <?php foreach ($this->layout()->allLangs as $langCode => $langName): ?> <?php if ($langCode !== $this->layout()->userLang) : ?> <li> - <button type="submit" class="btn <?=(count($this->layout()->allLangs) == 2) ? ' btn-secondary' : ''?>" href="#" onClick="document.langForm.mylang.value='<?=$langCode?>';document.langForm.submit()"> + <button type="submit" class="btn <?=(count($this->layout()->allLangs) == 2) ? ' btn-secondary' : ''?>" data-href="#" onClick="document.langForm.mylang.value='<?=$langCode?>';document.langForm.submit()"> <span class="visible-sm-md-only"><?=$langCode?></span> <span class="hidden-sm-md"><?=$this->displayLanguageOption($langName)?></span> </button> @@ -127,8 +127,8 @@ <?php /* finc searchbox: we use searchbox here so it becomes part of the sticky header, we need to place this after the navbar-right for anything but mobile - see flex-container in SCSS:*/ ?> <?php if ($this->layout()->searchbox !== false): ?> - <div class="search container"> - <nav class="nav searchbox hidden-print" role="search"> + <div class="search container" role="search"> + <nav class="nav searchbox hidden-print"> <?=$this->layout()->searchbox?> </nav> </div> diff --git a/themes/finc/templates/layout/layout.phtml b/themes/finc/templates/layout/layout.phtml index 898b0e194a6543dee78370def3c9e928c51b3e5e..8f4a34ab4c74a4cf5262f19e35e5843b65ff16ab 100644 --- a/themes/finc/templates/layout/layout.phtml +++ b/themes/finc/templates/layout/layout.phtml @@ -8,8 +8,20 @@ <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta name="viewport" content="width=device-width,initial-scale=1.0"/> <?=$this->headMeta()?> - <?=$this->headTitle()?> <?php + // Pullrequest 2157 Ticket #20826 + // Format the page title using the translation system: + $siteConfig = $this->config()->get('config')->Site; + $fullTitle = $this->translate( + 'title_wrapper', + [ + '%%pageTitle%%' => $this->headTitle()->renderTitle(), + '%%siteTitle%%' => $siteConfig->title, + '%%titleSeparator%%' => $siteConfig->titleSeparator ?? '::' + ] + ); + echo $this->headTitle($fullTitle, \Zend\View\Helper\Placeholder\Container\AbstractContainer::SET); + // Set up OpenSearch link: $this->headLink( [ @@ -245,7 +257,7 @@ if (!isset($this->layout()->searchbox)) { <!-- MODAL IN CASE WE NEED ONE --> <?php /* move X button to logical pos. in structure + make accessible via tab - CK */ ?> -<div id="modal" class="modal fade hidden-print" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-hidden="true" aria-describedby="modal-description"> +<div id="modal" class="modal fade hidden-print" tabindex="-1" role="dialog" aria-modal="true" aria-hidden="true" aria-describedby="modal-description"> <div class="modal-dialog"> <div class="modal-content"> <button type="button" class="close" data-dismiss="modal" tabindex="0" aria-label="<?= $this->transEsc('CloseModal') ?>"> diff --git a/themes/finc/templates/myresearch/account.phtml b/themes/finc/templates/myresearch/account.phtml index b1055a8c55baea6e4f76428bbad1ef93d11cedae..f4e40c2f045ce098ba788dd2cc29355671bfa88a 100644 --- a/themes/finc/templates/myresearch/account.phtml +++ b/themes/finc/templates/myresearch/account.phtml @@ -11,7 +11,6 @@ <?=$this->flashmessages()?> <form method="post" name="accountForm" id="accountForm" class="form-user-create" data-toggle="validator" role="form"> - <legend class="sr-only"><?=$this->transEsc('form-legend')?></legend> <?=$this->auth()->getCreateFields()?> <?=$this->recaptcha()->html($this->useRecaptcha) ?> <div class="form-group"> diff --git a/themes/finc/templates/myresearch/bulk-action-buttons.phtml b/themes/finc/templates/myresearch/bulk-action-buttons.phtml new file mode 100644 index 0000000000000000000000000000000000000000..0f70b338fb60343c7e44a4ec998bba4c2c7538f0 --- /dev/null +++ b/themes/finc/templates/myresearch/bulk-action-buttons.phtml @@ -0,0 +1,26 @@ +<!-- finc: myresearch - bulk-action-buttons --> +<?php if (isset($list)): ?> + <input type="hidden" name="listID" value="<?=$this->escapeHtmlAttr($list->id)?>" /> + <input type="hidden" name="listName" value="<?=$this->escapeHtmlAttr($list->title)?>" /> +<?php endif; ?> +<?php $user = $this->auth()->isLoggedIn(); ?> +<div class="bulkActionButtons"> + <div class="bulk-checkbox"> + <input type="checkbox" name="selectAll" class="checkbox-select-all" id="myresearchCheckAll"/> + <label for="myresearchCheckAll"><?=$this->transEsc('select_page')?> | <?=$this->transEsc('with_selected')?>:</label> + </div> + <div class="btn-group"> + <input id="<?=$this->idPrefix?>ribbon-email" class="btn btn-default" type="submit" name="email" value="<?=$this->transEsc('Email')?>" title="<?=$this->transEsc('email_selected')?>"/> + <?php if ((null !== $this->list && $this->list->editAllowed($user)) || null === $this->list && $user): ?> + <input class="btn btn-default" id="<?=$this->idPrefix?>delete_list_items_<?=null !== $this->list ? $this->escapeHtmlAttr($this->list->id) : ''?>" type="submit" name="delete" value="<?=$this->transEsc('Delete')?>" title="<?=$this->transEsc('delete_selected')?>"/> + <?php endif; ?> + <?php $exportOptions = $this->export()->getActiveFormats('bulk'); if (count($exportOptions) > 0): ?> + <input id="<?=$this->idPrefix?>ribbon-export" class="btn btn-default" type="submit" name="export" value="<?=$this->transEsc('Export')?>" title="<?=$this->transEsc('export_selected')?>"/> + <?php endif; ?> + <input id="<?=$this->idPrefix?>ribbon-print" class="btn btn-default" type="submit" name="print" value="<?=$this->transEsc('Print')?>" title="<?=$this->transEsc('print_selected')?>" data-lightbox-ignore/> + <?php if ($this->cart()->isActive()): ?> + <input class="btn btn-default" id="<?=$this->idPrefix?>updateCart" type="submit" name="add" value="<?=$this->transEsc('Add to Book Bag')?>"/> + <?php endif; ?> + </div> +</div> +<!-- finc: myresearch - bulk-action-buttons - END --> \ No newline at end of file diff --git a/themes/finc/templates/myresearch/cataloglogin.phtml b/themes/finc/templates/myresearch/cataloglogin.phtml index f5d936fb60ea2aa92d23096d32afc4f94b0d1059..0ae0ff0c85d5f04b38dacde8f2ea2eb076b066f0 100644 --- a/themes/finc/templates/myresearch/cataloglogin.phtml +++ b/themes/finc/templates/myresearch/cataloglogin.phtml @@ -37,7 +37,7 @@ <input id="profile_cat_password" type="password" name="cat_password" value="" class="form-control" autocomplete="current-password"/> </div> <div class="form-group"> - <input class="btn btn-primary" type="submit" name="processLogin" value="<?=$this->transEsc('Login')?>"> + <input class="btn btn-primary" type="submit" name="processLogin" aria-label="<?= $this->transEsc("Login-to-account") ?>" value="<?=$this->transEsc('Login')?>"> </div> </form> <?php endif; ?> diff --git a/themes/finc/templates/myresearch/menu.phtml b/themes/finc/templates/myresearch/menu.phtml index a2348fc565404adbfdcaae793f20c57a52d8b1f2..5178f89f59527145797307d0384824c85fa92e9b 100644 --- a/themes/finc/templates/myresearch/menu.phtml +++ b/themes/finc/templates/myresearch/menu.phtml @@ -12,7 +12,7 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php if ($this->userlist()->getMode() !== 'disabled'): ?> <li class="facet"> - <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active"' : ''?>> + <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active" aria-current="page"' : ''?>> <i class="fa fa-fw fa-star" aria-hidden="true"></i> <?=$this->transEsc('Favorites')?> </a> </li> @@ -22,7 +22,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php if ($this->ils()->checkCapability('getMyTransactions', $capabilityParams)): ?> <li class="facet"> - <a href="<?=$this->url('myresearch-checkedout')?>" class="flex checkedout<?=$this->active == 'checkedout' ? ' active' : ''?>"> + <a href="<?=$this->url('myresearch-checkedout')?>" class="flex checkedout<?=$this->active == 'checkedout' ? ' active' : ''?>" + <?=$this->active == 'checkedout' ? ' aria-current="page"' : ''?> + > <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 */ ?> @@ -32,7 +34,7 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php endif; ?> <?php if ($this->ils()->checkFunction('getMyTransactionHistory', $capabilityParams)): ?> <li class="facet"> - <a href="<?=$this->url('myresearch-historicloans')?>"<?=$this->active == 'historicloans' ? ' class="active"' : ''?>> + <a href="<?=$this->url('myresearch-historicloans')?>"<?=$this->active == 'historicloans' ? ' class="active" aria-current="page"' : ''?>> <i class="fa fa-fw fa-history" aria-hidden="true"></i> <?=$this->transEsc('Loan History')?> </a> </li> @@ -40,7 +42,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php if ($this->ils()->checkCapability('getMyHolds', $capabilityParams)): ?> <li class="facet"> - <a href="<?=$this->url('myresearch-holds')?>" class="flex<?=$this->active == 'holds' ? ' active' : ''?>"> + <a href="<?=$this->url('myresearch-holds')?>" class="flex<?=$this->active == 'holds' ? ' active' : ''?>" + <?=$this->active == 'holds' ? ' aria-current="page"' : ''?> + > <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 */ ?> @@ -51,7 +55,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php if ($this->ils()->checkFunction('StorageRetrievalRequests', $capabilityParams)): ?> <li class="facet"> - <a href="<?=$this->url('myresearch-storageretrievalrequests')?>" class="flex<?=$this->active == 'storageRetrievalRequests' ? ' active' : ''?>"> + <a href="<?=$this->url('myresearch-storageretrievalrequests')?>" class="flex<?=$this->active == 'storageRetrievalRequests' ? ' active' : ''?>" + <?=$this->active == 'storageRetrievalRequests' ? ' aria-current="page"' : ''?> + > <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 */ ?> @@ -62,7 +68,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php if ($this->ils()->checkFunction('ILLRequests', $capabilityParams)): ?> <li class="facet"> - <a href="<?=$this->url('myresearch-illrequests')?>" class="flex<?=$this->active == 'ILLRequests' ? ' active' : ''?>"> + <a href="<?=$this->url('myresearch-illrequests')?>" class="flex<?=$this->active == 'ILLRequests' ? ' active' : ''?>" + <?=$this->active == 'ILLRequests' ? ' aria-current="page"' : ''?> + > <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 */ ?> @@ -73,20 +81,22 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php if ($this->ils()->checkCapability('getMyFines', $capabilityParams)): ?> <li class="facet"> - <a href="<?=$this->url('myresearch-fines')?>" class="flex<?=$this->active == 'fines' ? ' active' : ''?>"> + <a href="<?=$this->url('myresearch-fines')?>" class="flex<?=$this->active == 'fines' ? ' active' : ''?>" + <?=$this->active == 'fines' ? ' aria-current="page"' : ''?> + > <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; ?> <li class="facet"> - <a href="<?=$this->url('myresearch-profile')?>"<?=$this->active == 'profile' ? ' class="active"' : ''?>> + <a href="<?=$this->url('myresearch-profile')?>"<?=$this->active == 'profile' ? ' class="active" aria-current="page"' : ''?>> <i class="fa fa-fw fa-user" aria-hidden="true"></i> <?=$this->transEsc('Profile')?> </a> </li> <?php if ($user && $user->libraryCardsEnabled()): ?> <li class="facet"> - <a href="<?=$this->url('librarycards-home')?>"<?=$this->active == 'librarycards' ? ' class="active"' : ''?>> + <a href="<?=$this->url('librarycards-home')?>"<?=$this->active == 'librarycards' ? ' class="active" aria-current="page"' : ''?>> <i class="fa fa-fw fa-barcode" aria-hidden="true"></i> <?=$this->transEsc('Library Cards')?> </a> </li> @@ -94,7 +104,7 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php endif; ?> <?php if ($this->accountCapabilities()->getSavedSearchSetting() === 'enabled'): ?> <li class="facet"> - <a href="<?=$this->url('search-history')?>?require_login"<?=$this->active == 'history' ? ' class="active"' : ''?>> + <a href="<?=$this->url('search-history')?>?require_login"<?=$this->active == 'history' ? ' class="active" aria-current="page"' : ''?>> <i class="fa fa-fw fa-search" aria-hidden="true"></i> <?=$this->transEsc('history_saved_searches')?> </a> </li> @@ -112,7 +122,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <h3><?=$this->transEsc('Preferences')?></h3> <ul class="myresearch-menu facet-group"> <li class="facet"> - <a href="<?=$this->url('myresearch-changepassword')?>"<?=$this->active == 'newpassword' ? ' class="active"' : ''?>> + <a href="<?=$this->url('myresearch-changepassword')?>"<?=$this->active == 'newpassword' ? ' class="active"' : ''?> + <?=$this->active == 'newpassword' ? ' aria-current="page"' : ''?> + > <i class="fa fa-fw fa-lock" aria-hidden="true"></i> <?=$this->transEsc('Change Password')?> </a> </li> @@ -123,7 +135,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <h3><?=$this->transEsc('Your Lists')?></h3> <ul class="myresearch-menu facet-group"> <li class="facet"> - <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active"' : ''?>> + <a href="<?=$this->url('myresearch-favorites')?>"<?=$this->active == 'favorites' ? ' class="active"' : ''?> + <?=$this->active == 'favorites' ? ' aria-current="page"' : ''?> + > <i class="fa fa-fw fa-star" aria-hidden="true"></i> <?=$this->transEsc('Your Favorites')?> </a> </li> @@ -132,7 +146,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php foreach ($lists as $list): ?> <?php /* finc: keep icon inside + keep braces in badge!; CK*/ ?> <li class="facet"> - <a href="<?=$this->url('userList', ['id' => $list['id']])?>"<?=$this->active == 'list' . $list['id'] ? ' class="active"' : ''?>> + <a href="<?=$this->url('userList', ['id' => $list['id']])?>"<?=$this->active == 'list' . $list['id'] ? ' class="active"' : ''?> + <?=$this->active == 'list' . $list['id'] ? ' aria-current="page"' : ''?> + > <i class="fa fa-fw fa-star-o" aria-hidden="true"></i> <?=$this->escapeHtml($list['title'])?> <span class="badge">(<?=$list->cnt?>)</span> </a> @@ -140,7 +156,9 @@ $capabilityParams = $patron ? ['patron' => $patron] : []; <?php endforeach; ?> <li class="facet"> - <a href="<?=$this->url('editList', ['id' => 'NEW'])?>"<?=$this->active == 'editlist/NEW' ? ' class="active"' : ''?>> + <a href="<?=$this->url('editList', ['id' => 'NEW'])?>"<?=$this->active == 'editlist/NEW' ? ' class="active"' : ''?> + <?=$this->active == 'editlist/NEW' ? ' aria-current="page"' : ''?> + > <i class="fa fa-fw fa-plus" aria-hidden="true"></i> <?=$this->transEsc('Create a List')?> </a> </li> diff --git a/themes/finc/templates/myresearch/mylist.phtml b/themes/finc/templates/myresearch/mylist.phtml index 6354ae6aa1c7962981d456fc50ba0388082b6014..ed7bf00413d20dec9fd6795100a4b9f99b4e0d0c 100644 --- a/themes/finc/templates/myresearch/mylist.phtml +++ b/themes/finc/templates/myresearch/mylist.phtml @@ -57,7 +57,7 @@ $user = $this->auth()->isLoggedIn(); </a> <ul class="dropdown-menu"> <li><a href="<?=$this->url('myresearch-deletelist')?>?listID=<?=urlencode($list->id)?>&confirm=1"><?=$this->transEsc('confirm_dialog_yes')?></a></li> - <li><a href="#"><?=$this->transEsc('confirm_dialog_no')?></a></li> + <li><a href="#" class="dropdown-abort"><?=$this->transEsc('confirm_dialog_no')?></a></li> </ul> </div> <?php endif; ?> @@ -95,5 +95,4 @@ $user = $this->auth()->isLoggedIn(); <br/><?=$this->recommend($current)?> <?php endforeach; ?> </div> - -<!-- finc: myresearch - mylist - END --> \ No newline at end of file +<!-- finc: myresearch - mylist - END --> diff --git a/themes/finc/templates/myresearch/newpassword.phtml b/themes/finc/templates/myresearch/newpassword.phtml index b2102e7315a77aac2af970fed31039ebc427488a..e493278a4bcba753d7aed4bbb79edfc44bffe16a 100644 --- a/themes/finc/templates/myresearch/newpassword.phtml +++ b/themes/finc/templates/myresearch/newpassword.phtml @@ -23,7 +23,6 @@ <div class="error"><?=$this->transEsc('recovery_user_not_found') ?></div> <?php else: ?> <form id="newpassword" class="form-new-password" action="<?=$this->url('myresearch-newpassword') ?>" method="post" data-toggle="validator" role="form"> - <legend class="sr-only"><?=$this->transEsc('form-legend')?></legend> <input type="hidden" value="<?=$this->escapeHtmlAttr($this->auth()->getManager()->getCsrfHash())?>" name="csrf"/> <input type="hidden" value="<?=$this->escapeHtmlAttr($this->hash) ?>" name="hash"/> <input type="hidden" value="<?=$this->escapeHtmlAttr($this->username) ?>" name="username"/> diff --git a/themes/finc/templates/myresearch/setpin.phtml b/themes/finc/templates/myresearch/setpin.phtml new file mode 100644 index 0000000000000000000000000000000000000000..7d57c304752daa033d9e22475f432ca036205df7 --- /dev/null +++ b/themes/finc/templates/myresearch/setpin.phtml @@ -0,0 +1,66 @@ +<!-- finc: myresearch - setpin --> +<?php +// Set up page title: +$this->headTitle($this->translate('LiberoAccount::change_user_pin')); + +// Set up breadcrumbs: +$this->layout()->breadcrumbs = '<li><a href="' . $this->url('myresearch-home') . '">' . $this->transEsc('Your Account') . '</a></li>' + . '<li class="active">' . $this->transEsc('LiberoAccount::change_user_pin') . '</li>'; +?> +<?php if ($this->auth()->isLoggedIn()): ?> + <a class="search-filter-toggle visible-xs" href="#myresearch-sidebar" data-toggle="offcanvas" aria-label="<?=$this->transEsc('sidebar_expand')?>"> + <?=$this->transEsc('Your Account') ?> + </a> + <div class="<?=$this->layoutClass('mainbody')?>"> +<?php endif; ?> + + <h1><?=$this->transEsc('LiberoAccount::change_user_pin')?></h1> + <p class="alert alert-info"><?=$this->transEsc('LiberoAccount::set_pin_note')?></p> + <?=$this->flashmessages()?> + + <?php if (!$this->auth()->getManager()->supportsPasswordChange($this->auth_method)): ?> + <div class="error"><?=$this->transEsc('recovery_new_disabled')?></div> + <?php elseif (!isset($this->hash)): ?> + <div class="error"><?=$this->transEsc('recovery_user_not_found')?></div> + <?php else: ?> + <form id="setpin" class="form-set-pin" action="<?=$this->url('myresearch-setpin')?>" method="post" data-toggle="validator" role="form"> + <input type="hidden" value="<?=$this->escapeHtmlAttr($this->auth()->getManager()->getCsrfHash(true))?>" name="csrf"/> + <input type="hidden" value="<?=$this->escapeHtmlAttr($this->hash)?>" name="hash"/> + <input type="hidden" value="<?=$this->escapeHtmlAttr($this->username)?>" name="username"/> + <input type="hidden" value="<?=$this->escapeHtmlAttr($this->auth_method)?>" name="auth_method"/> + + <?php if (isset($this->cat_username)): ?> + <div class="form-group"> + <label class="control-label"><?=$this->transEsc('Username')?>:</label> + <p class="form-control"><?=$this->cat_username?></p> + </div> + <?php endif; ?> + + <div class="form-group"> + <label for="pin" class="control-label"><?=$this->transEsc('LiberoAccount::new_pin')?>:</label> + <input type="password" id="pin" name="pin" class="form-control" required aria-required="true" aria-describedby="pin-policy-error" + <?=isset($this->passwordPolicy['minLength']) ? ' data-minlength="' . $this->passwordPolicy['minLength'] . '" data-minlength-error="' . $this->escapeHtmlAttr($this->translate('password_minimum_length', array('%%minlength%%' => $this->passwordPolicy['minLength']))) . '"' : ''?> + <?=isset($this->passwordPolicy['maxLength']) ? ' maxlength="' . $this->passwordPolicy['maxLength'] . '"' : ''?> pattern="[0-9]{4}" /> + <div id="pin-policy-error" class="help-block with-errors"></div> + </div> + + <div class="form-group"> + <label for="confirm-pin" class="control-label"><?=$this->transEsc('LiberoAccount::confirm_new_pin')?>:</label> + <input type="password" id="confirm-pin" name="pin2" class="form-control" required aria-required="true" data-match="#pin" data-match-error="<?=$this->escapeHtmlAttr($this->translate('LiberoAccount::new_pin_missmatch'))?>" aria-describedby="pin-confirmation-error"/> + <div id="pin-confirmation-error" class="help-block with-errors"></div> + </div> + + <div class="form-group"> + <input class="btn btn-primary" name="submit" type="submit" value="<?=$this->transEsc('LiberoAccount::change_user_pin')?>"/> + </div> + </form> + <?php endif; ?> + +<?php if ($this->auth()->isLoggedIn()): ?> + </div> + + <div class="<?=$this->layoutClass('sidebar')?>"> + <?=$this->context($this)->renderInContext("myresearch/menu.phtml", ['active' => 'setpin'])?> + </div> +<?php endif; ?> +<!-- finc: myresearch - setpin - END --> diff --git a/themes/finc/templates/record/cart-buttons.phtml b/themes/finc/templates/record/cart-buttons.phtml index edb7d83ea32988fbf3970619cf617a6174abcc5b..beaa7cbe11028a7347ce015293c8cd923e53f0b9 100644 --- a/themes/finc/templates/record/cart-buttons.phtml +++ b/themes/finc/templates/record/cart-buttons.phtml @@ -3,7 +3,7 @@ <?php if ($cart->isActive()): ?> <?php $cartId = $this->source . '|' . $this->id; ?> - <span class="btn-bookbag-toggle" data-cart-id="<?=$this->escapeHtmlAttr($this->id)?>" data-cart-source="<?=$this->escapeHtmlAttr($this->source)?>"> + <div class="btn-bookbag-toggle" data-cart-id="<?=$this->escapeHtmlAttr($this->id)?>" data-cart-source="<?=$this->escapeHtmlAttr($this->source)?>"> <?php /* Make add-to/remove-from bookbag accessible for keyboard navigation - CK */ ?> <a class="cart-add hidden<?php if (!$cart->contains($cartId)): ?> correct<?php endif ?>" href="javascript:" tabindex="0"> <i class="cart-link-icon fa fa-plus" aria-hidden="true"></i><span class="cart-link-label"><?=$this->transEsc('Add to Book Bag')?></span> @@ -11,16 +11,17 @@ <a class="cart-remove hidden<?php if ($cart->contains($cartId)): ?> correct<?php endif ?>" href="javascript:" tabindex="0"> <i class="cart-link-icon fa fa-minus-circle" aria-hidden="true"></i> <span class="cart-link-label"><?=$this->transEsc('Remove from Book Bag')?></span> </a> - <noscript> - <form method="post" name="addForm" action="<?=$this->url('cart-processor')?>"> + <form class="cartProcessorNoJs" method="post" name="addForm" action="<?=$this->url('cart-processor')?>"> + <noscript> <input type="hidden" name="ids[]" value="<?=$this->escapeHtmlAttr($cartId)?>"/> <?php if ($cart->contains($cartId)): ?> <input class="btn btn-default" type="submit" name="delete" value="<?=$this->transEsc('Remove from Book Bag')?>"/> <?php else: ?> <input class="btn btn-default" type="submit" name="add" value="<?=$this->transEsc('Add to Book Bag')?>"/> <?php endif; ?> - </form> - </noscript> - </span> + </noscript> + </form> + <script>$(document).ready(function() { $(".cartProcessorNoJs").css('display', 'none'); });</script> + </div> <?php endif; ?> <!-- finc: record - cart-buttons END --> diff --git a/themes/finc/templates/record/cover.phtml b/themes/finc/templates/record/cover.phtml index e557d2210f79a7de886fcd9fde892f794e09b6af..3d0b10c5e0f9e40e472b9ab06193ed40ef5ca547 100644 --- a/themes/finc/templates/record/cover.phtml +++ b/themes/finc/templates/record/cover.phtml @@ -3,7 +3,13 @@ <?php /* If you want to load covers in lightbox use .recordcover; the class .nocover prevents nocover images from loading in lightbox or can be used to hide the images */ ?> <?php $alt = $alt ?? $this->transEsc('Cover Image')?> -<?php $coverId = "cover-" . $driver->getUniqueID() . "-" . time(); ?> +<?php $id = $driver->getUniqueID() ?> +<?php if ($id) { + $coverId = "cover-$id-" . time(); +} else { + $cover = false; +} +?> <?php if ($cover): ?> <?php if ($this->link): ?><a id="<?=$coverId?>" href="<?=$this->escapeHtmlAttr($this->link)?>" tabindex="-1" aria-hidden="true"><?php endif; ?> <img alt="<?=$alt?>" <?php if ($linkPreview): ?>data-linkpreview="true" <?php endif; ?>class="recordcover" src="<?=$this->escapeHtmlAttr($cover); ?>" /> @@ -21,7 +27,7 @@ <div class="cover-container"> <?=$this->render('record/coverReplacement')?> <a class="coverlink hidden" aria-hidden="true" tabindex="-1"> - <img <?php if ($linkPreview): ?>data-linkpreview="true" <?php endif; ?> class="recordcover ajax" alt="<?=$this->escapeHtmlAttr($alt); ?>" /> + <img <?php if ($linkPreview): ?>data-linkpreview="true" <?php endif; ?> class="recordcover ajax" src="<?=$this->imageLink('noCover2.gif')?>" alt="<?=$this->escapeHtmlAttr($alt); ?>" /> </a> <script> loadCoverByElement({source:'<?=$this->escapeHtmlAttr($driver->getSourceIdentifier())?>', recordId:'<?=$this->escapeHtmlAttr($driver->getUniqueID())?>', size:'<?=$this->escapeHtmlAttr($size)?>'}, $('#<?=$coverId?>')); diff --git a/themes/finc/templates/record/pdaform.phtml b/themes/finc/templates/record/pdaform.phtml index a7faa1f04c5d4fdb60d1075833acab13f2d9eb4f..ab7cb4438655e44e234fa26104d70738ca39a50a 100644 --- a/themes/finc/templates/record/pdaform.phtml +++ b/themes/finc/templates/record/pdaform.phtml @@ -36,8 +36,14 @@ $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink <?=$this->recaptcha()->html($this->useRecaptcha)?> </span> <input type="submit" class="btn btn-primary" role="button" name="submit" value="<?=$this->transEsc('Submit')?>"/> - <a class="btn btn-primary" data-lightbox-ignore data-toggle="tooltip" title="<?=$this->transEsc('PDA::pda_open_new_window')?>" href="<?=$this->interlibraryloan()->getSwbLink($this->driver)?>" target="_blank"><?=$this->transEsc('PDA::pda_tab_interlibrary_button')?> - </a> + <?= $this->externalLink( + $this->interlibraryloan()->getSwbLink($this->driver), + $this->translate('PDA::pda_tab_interlibrary_button'), + ['class' => 'btn btn-primary', + 'data-lightbox-ignore' => '', + 'data-toggle' => 'tooltip', + 'title' => $this->translate('PDA::pda_open_new_window')] + ) ?> <button class="btn btn-transparent" type="button" data-dismiss="modal" href="#"><?=$this->transEsc('Reset')?></button> </div> diff --git a/themes/finc/templates/record/view.phtml b/themes/finc/templates/record/view.phtml index 1f97d387fad4ff410d80c391850a4220a5bfe5a1..076ef6fb512970578a5aac9a2f531bdacdc6a6c8 100644 --- a/themes/finc/templates/record/view.phtml +++ b/themes/finc/templates/record/view.phtml @@ -40,15 +40,17 @@ <?= $this->record($this->driver)->getCoreMetadata() ?> <?php if (count($this->tabs) > 0): ?> - <a name="tabnav"></a> + <?php /* swap deprecated 'name' for 'ID' - CK */ ?> + <a id="tabnav"></a> <div class="record-tabs"> + <?php /* DO NOT add 'role=tablist' for accessibility, see #19938 - CK */ ?> <ul class="nav nav-tabs"> <?php foreach ($this->tabs as $tab => $obj): ?> <?php // add current tab to breadcrumbs if applicable: $desc = $obj->getDescription(); $tabName = preg_replace("/\W/", "-", strtolower($tab)); $tabClasses = ['record-tab', $tabName]; - if (0 === strcasecmp($this->activeTab, $tab)) { + if (($isActiveTab = 0 === strcasecmp($this->activeTab, $tab))) { if (!$this->loadInitialTabWithAjax || !$obj->supportsAjax()) { $tabClasses[] = 'active'; } @@ -63,16 +65,27 @@ $tabClasses[] = 'noajax'; } ?> + <?php /* DO NOT add role="tab" BUT DO ADD aria-controls and ID for accessibility -- + 'aria-selected' (true/false) needs to be set via record.js - CK */ ?> <li class="<?= implode(' ', $tabClasses) ?>" data-tab="<?= $tabName ?>"> - <a - href="<?= $this->recordLink()->getTabUrl($this->driver, $tab) ?>#tabnav"<?php if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)): ?> data-background<?php endif ?>><?= $this->transEsc($desc) ?></a> + <a href="<?= $this->recordLink()->getTabUrl($this->driver, $tab) ?>#tabnav" + <?php if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)): ?> data-background<?php endif ?> + aria-selected="<?= $isActiveTab ? "true" : "false" ?>" + <?php if($isActiveTab): ?>aria-controls="<?= $tabName ?>"<?php endif; ?> + id="<?= $tabName ?>-tabselector"> + <?= $this->transEsc($desc) ?> + <span class="sr-only load-tab-content"><?= $this->transEsc('load_tab_content_hint') ?></span></a> </li> <?php endforeach; ?> </ul> - <div class="tab-content"> + <div class="tab-content" aria-live="polite" tabindex="-1"> <?php if (!$this->loadInitialTabWithAjax || !isset($activeTabObj) || !$activeTabObj->supportsAjax()): ?> - <div class="tab-pane active <?= $this->escapeHtmlAttr($this->activeTab) ?>-tab"> + <?php /* Add ID, role and aria-labelledby for accessibility - CK */ ?> + <div class="tab-pane active <?= $this->escapeHtmlAttr($this->activeTab) ?>-tab" + role="tabpanel" + id="<?= $this->escapeHtmlAttr($this->activeTab) ?>" + aria-labelledby="<?= $this->activeTab ?>-tabselector"> <?= isset($activeTabObj) ? $this->record($this->driver)->getTab($activeTabObj) : '' ?> </div> <?php endif; ?> diff --git a/themes/finc/templates/search/advanced/layout.phtml b/themes/finc/templates/search/advanced/layout.phtml index 73abd574cfc985ee40bfa185163f42a77fb0807a..350eabbcb32299defd5719084c250809c70a3ffc 100644 --- a/themes/finc/templates/search/advanced/layout.phtml +++ b/themes/finc/templates/search/advanced/layout.phtml @@ -123,12 +123,12 @@ if (isset($searchDetails) && is_object($searchDetails)) { <label for="search_lookfor<?=$group . '_' . $search?>"> <?=$this->transEsc("search_terms")?>: </label> - <input name="lookfor<?=$group ?>[]" id="search_lookfor<?=$group . '_' . $search ?>" class="adv-term-input form-control" type="text"<?php if (isset($setQueries[$group][$search])): ?> value="<?=$this->escapeHtml($setQueries[$group][$search]->getString())?>"<?php endif; ?> aria-label="<?=$this->transEsc("search_terms")?>"> + <input name="lookfor<?=$group ?>[]" id="search_lookfor<?=$group . '_' . $search ?>" class="adv-term-input form-control" type="text"<?php if (isset($setQueries[$group][$search])): ?> value="<?=$this->escapeHtml($setQueries[$group][$search]->getString())?>"<?php endif; ?>> </div> <div class="adv-select"> <label for="type<?=$group . '_' . $search?>"><?=$this->transEsc("Search type")?>:</label> - <select id="type<?=$group . '_' . $search?>" class="adv-term-type form-control" name="type<?=$group?>[]" aria-label="<?=$this->transEsc("Search type")?>"> + <select id="type<?=$group . '_' . $search?>" class="adv-term-type form-control" name="type<?=$group?>[]"> <?php foreach ($this->options->getAdvancedHandlers() as $searchVal => $searchDesc): ?> <option value="<?=$this->escapeHtml($searchVal)?>"<?php if(isset($setQueries[$group][$search]) && $searchVal == $setQueries[$group][$search]->getHandler()):?> selected<?php endif;?>><?=$this->transEsc($searchDesc)?></option> <?php endforeach; ?> @@ -157,7 +157,7 @@ if (isset($searchDetails) && is_object($searchDetails)) { </div> <div class="adv-group-match"> <label class="search_bool" for="search_bool<?=$group ?>"><?=$this->transEsc("search_match")?>: </label> - <select id="search_bool<?=$group?>" name="bool<?=$group?>[]" class="form-control"> + <select id="search_bool<?=$group?>" name="bool<?=$group?>[]" class="form-control"> <option value="AND"<?php if(isset($setSearchGroups[$group]) && 'AND' == $setSearchGroups[$group]):?> selected<?php endif;?>><?=$this->transEsc("search_AND")?></option> <option value="OR"<?php if(isset($setSearchGroups[$group]) && 'OR' == $setSearchGroups[$group]):?> selected<?php endif;?>><?=$this->transEsc("search_OR")?></option> <option value="NOT"<?php if(isset($setSearchGroups[$group]) && 'NOT' == $setSearchGroups[$group]):?> selected<?php endif;?>><?=$this->transEsc("search_NOT")?></option> diff --git a/themes/finc/templates/search/bulk-action-buttons.phtml b/themes/finc/templates/search/bulk-action-buttons.phtml index cc40884afe28ff37699faaf0e3e6d1483eb120b3..caa79a1ba818f44c22f100ed276a2323a0b07411 100644 --- a/themes/finc/templates/search/bulk-action-buttons.phtml +++ b/themes/finc/templates/search/bulk-action-buttons.phtml @@ -10,13 +10,13 @@ </div> <div class="btn-group"> <?php if (isset($this->showBulkOptions) && $this->showBulkOptions): ?> - <input id="ribbon-email" class="btn btn-transparent" type="submit" name="email" title="<?=$this->transEsc('bookbag_email_selected')?>" value="<?=$this->transEsc('Email')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> + <input id="<?=$this->idPrefix?>ribbon-email" class="btn btn-transparent" type="submit" name="email" title="<?=$this->transEsc('bookbag_email_selected')?>" value="<?=$this->transEsc('Email')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> <?php $exportOptions = $this->export()->getBulkOptions(); if (count($exportOptions) > 0): ?> - <input id="ribbon-export" class="btn btn-transparent" type="submit" name="export" title="<?=$this->transEsc('bookbag_export_selected')?>" value="<?=$this->transEsc('Export')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> + <input id="<?=$this->idPrefix?>ribbon-export" class="btn btn-transparent" type="submit" name="export" title="<?=$this->transEsc('bookbag_export_selected')?>" value="<?=$this->transEsc('Export')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> <?php endif; ?> - <input id="ribbon-print" class="btn btn-transparent" type="submit" name="print" title="<?=$this->transEsc('bookbag_print_selected')?>" value="<?=$this->transEsc('Print')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> + <input id="<?=$this->idPrefix?>ribbon-print" class="btn btn-transparent" type="submit" name="print" title="<?=$this->transEsc('bookbag_print_selected')?>" value="<?=$this->transEsc('Print')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> <?php if ($this->userlist()->getMode() !== 'disabled'): ?> - <input id="ribbon-save" class="btn btn-transparent" type="submit" name="saveCart" title="<?=$this->transEsc('bookbag_save_selected')?>" value="<?=$this->transEsc('Save')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> + <input id="<?=$this->idPrefix?>ribbon-save" class="btn btn-transparent" type="submit" name="saveCart" title="<?=$this->transEsc('bookbag_save_selected')?>" value="<?=$this->transEsc('Save')?>"<?php if($this->formAttr):?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>/> <?php endif; ?> <?php endif; ?> <?php if (isset($this->showCartControls) && $this->showCartControls): ?> diff --git a/themes/finc/templates/search/results.phtml b/themes/finc/templates/search/results.phtml index de9ade6fd08919a365af15829592376107b95f24..8bac2e91e997dee739e6556a1349061de5c306b5 100644 --- a/themes/finc/templates/search/results.phtml +++ b/themes/finc/templates/search/results.phtml @@ -85,15 +85,15 @@ $this->headScript()->appendFile("check_save_statuses.js"); <?php if ($recordTotal > 0): ?> <?php /* finc: use spans for easier to show/hide choices - CK */ ?> <div class="search-controls"> - <span class="limit"> + <div class="limit"> <?=$this->render('search/controls/limit.phtml')?> - </span> - <span class="sort right"> + </div> + <div class="sort right"> <?=$this->render('search/controls/sort.phtml')?> - </span> - <span class="view"> + </div> + <div class="view"> <?=$this->render('search/controls/view.phtml')?> - </span> + </div> </div> <?php endif; ?> </div> diff --git a/themes/finc/theme.config.php b/themes/finc/theme.config.php index 3896a57fa8beb2d1e681c1a4b599d47299116f8c..652545a3d02d4d550bb1be2fc57cace7edd3cac8 100644 --- a/themes/finc/theme.config.php +++ b/themes/finc/theme.config.php @@ -5,8 +5,7 @@ return [ 'check_item_statuses.js', 'lightbox_form_cache.js', 'covers.js', - 'common-finc.js', - 'cart-finc.js' + 'common-finc.js' ], 'helpers' => [ 'aliases' => [ @@ -18,7 +17,6 @@ return [ 'recordLink' => 'finc\View\Helper\Root\RecordLink', 'record' => 'finc\View\Helper\Root\Record', 'flashmessages' => 'finc\View\Helper\Root\Flashmessages', - 'headTitle' => 'finc\View\Helper\Root\HeadTitle', 'externalLink' => 'finc\View\Helper\Root\ExternalLink', ], 'factories' => [ @@ -44,8 +42,6 @@ return [ 'VuFind\View\Helper\Root\ResultFeedFactory', 'finc\View\Helper\Root\Flashmessages' => 'VuFind\View\Helper\Root\FlashmessagesFactory', - 'finc\View\Helper\Root\HeadTitle' => - 'finc\View\Helper\Root\Factory::getHeadTitle', 'finc\View\Helper\Root\ExternalLink' => 'finc\View\Helper\Root\Factory::getExternalLink', ]