diff --git a/devops/docker/autoconfig/entrypoint.sh b/devops/docker/autoconfig/entrypoint.sh
index e8bcdc261a5f5677c43bf2161bcd7e546c166ba7..8213ec03d8a0b897dd6805047ec0eb8561dd7acb 100755
--- a/devops/docker/autoconfig/entrypoint.sh
+++ b/devops/docker/autoconfig/entrypoint.sh
@@ -18,7 +18,7 @@
 # @license https://opensource.org/licenses/GPL-3.0 GNU GPLv3
 
 if [ "$(whoami)" = "node" ]; then
-  [ -d "$HOME" ] || PREFIX=$HOME npm install -g @ubleipzig/autoconfig@2.3.2
+  [ -d "$HOME" ] || PREFIX=$HOME npm install -g @ubleipzig/autoconfig@2.3.3
   echo "$VUFIND_DEFAULTS" > "./data/docker/autoconfig/$VUFIND_SITE.json"
   exec "$HOME/bin/autoconfig" -c ./data/docker/autoconfig "$@"
   exit
diff --git a/docker-env.0.finc.yml b/docker-env.0.finc.yml
index d3766a42100edd5c832883b6ff46721d96ba548e..68452924bcae45c4fe104bf7a89b2719937c5d53 100644
--- a/docker-env.0.finc.yml
+++ b/docker-env.0.finc.yml
@@ -55,5 +55,7 @@ autoconfig:
         password: dev
     searches.ini:
       IndexShards: {}
+    searchspecs.yaml:
+      "@parent_yaml": "../../../config/vufind/searchspecs.yaml"
 
 mail: {}
\ No newline at end of file
diff --git a/local/alpha/config/vufind/searchspecs.yaml b/local/alpha/config/vufind/searchspecs.yaml
index a14e9f67517e72cd14a6935db4f32b5c3f289f91..0f5518b83044f980aa4385c47fcad1be7019104e 100644
--- a/local/alpha/config/vufind/searchspecs.yaml
+++ b/local/alpha/config/vufind/searchspecs.yaml
@@ -1,779 +1,2 @@
 ---
-# Listing of search types and their component parts and weights.
-#
-# Format is:
-#  searchType:
-#    # CustomMunge is an optional section to define custom pre-processing of
-#    #     user input.  See below for details of munge actions.
-#    CustomMunge:
-#      MungeName1:
-#        - [action1, actionParams]
-#        - [action2, actionParams]
-#        - [action3, actionParams]
-#      MungeName2:
-#        - [action1, actionParams]
-#    # DismaxFields is optional and defines the fields sent to the Dismax handler
-#    #     when we are able to use it.  QueryFields will be used for advanced
-#    #     searches that Dismax cannot support.  QueryFields is always used if no
-#    #     DismaxFields section is defined.
-#    DismaxFields:
-#      - field1^boost
-#      - field2^boost
-#      - field3^boost
-#    # DismaxParams is optional and allows you to override default Dismax settings
-#    #     (i.e. mm / bf) on a search-by-search basis. Enclose the parameter values
-#    #     in quotes for proper behavior. If you want global default values for these
-#    #     settings, you can edit the appropriate search handler in
-#    #     solr/biblio/conf/solrconfig.xml.
-#    DismaxParams:
-#      - [param1_name, param1_value]
-#      - [param2_name, param2_value]
-#      - [param3_name, param3_value]
-#    # This optional setting may be used to specify which Dismax handler to use. By
-#    #     default, VuFind provides two options: dismax (for the old, standard
-#    #     Dismax) and edismax (for Extended Dismax). You can also configure your own
-#    #     in solrconfig.xml, but VuFind relies on the name "edismax" to identify an
-#    #     Extended Dismax handler. If you omit this setting, the default value from
-#    #     the default_dismax_handler setting in the [Index] section of config.ini
-#    #     will be used.
-#    DismaxHandler: dismax|edismax
-#    # QueryFields define the fields we are searching when not using Dismax; VuFind
-#    #     detects queries that will not work with Dismax and switches to QueryFields
-#    #     as needed.
-#    QueryFields:
-#      SolrField:
-#        - [howToMungeSearchstring, weight]
-#        - [differentMunge, weight]
-#      DifferentSolrField:
-#        - [howToMunge, weight]
-#    # The optional FilterQuery section allows you to AND a static query to the
-#    #     dynamic query generated using the QueryFields; see JournalTitle below
-#    #     for an example.  This is applied whether we use DismaxFields or
-#    #     QueryFields.
-#    FilterQuery: (optional Lucene filter query)
-#    ExactSettings:
-#      DismaxFields: ...
-#      QueryFields: ...
-#    # All the same settings as above, but for exact searches, i.e. search terms
-#    #     enclosed in quotes. Allows different fields or weights for exact
-#    #     searches. See below for commented-out examples.
-#
-# ...etc.
-#
-#-----------------------------------------------------------------------------------
-#
-# Within the QueryFields area, fields are OR'd together, unless they're in an
-# anonymous array with a numeric instead of alphanumeric key, in which case the
-# first element is a two-value array that tells us what the type (AND or OR) and
-# weight of the whole group should be.
-#
-# So, given:
-#
-# test:
-#   QueryFields:
-#     A:
-#       - [onephrase, 500]
-#       - [and, 200]
-#     B:
-#       - [and, 100]
-#       - [or, 50]
-#     # Start an anonymous array to group; first element indicates AND grouping
-#     #     and a weight of 50
-#     0:
-#       0:
-#         - AND
-#         - 50
-#       C:
-#         - [onephrase, 200]
-#       D:
-#         - [onephrase, 300]
-#       # Note the "not" attached to the field name as a minus, and the use of ~
-#       #     to mean null ("no special weight")
-#       -E:
-#         - [or, ~]
-#     D:
-#       - [or, 100]
-#
-#  ...and the search string
-#
-#      test "one two"
-#
-#  ...we'd get
-#
-#   (A:"test one two"^500 OR
-#    A:(test AND "one two")^ 200 OR
-#    B:(test AND "one two")^100 OR
-#    B:(test OR "one two")^50
-#    (
-#      C:("test one two")^200 AND
-#      D:"test one two"^300 AND
-#      -E:(test OR "one two")
-#    )^50 OR
-#    D:(test OR "one two")^100
-#   )
-#
-#-----------------------------------------------------------------------------------
-#
-# Munge types are based on the original Solr.php code, and consist of:
-#
-# onephrase: eliminate all quotes and do it as a single phrase. 
-#   testing "one two"
-#    ...becomes ("testing one two")
-#
-# and: AND the terms together
-#  testing "one two"
-#   ...becomes (testing AND "one two")
-#
-# or: OR the terms together
-#  testing "one two"
-#   ...becomes (testing OR "one two")
-#
-# identity: Use the search as-is
-#  testing "one two"
-#   ...becomes (testing "one two")
-#
-# Additional Munge types can be defined in the CustomMunge section.  Each array
-# entry under CustomMunge defines a new named munge type.  Each array entry under
-# the name of the munge type specifies a string manipulation operation.  Operations
-# will be applied in the order listed, and different operations take different
-# numbers of parameters.
-#
-# Munge operations:
-#
-# [append, text] - Append text to the end of the user's search string
-# [lowercase] - Convert string to lowercase
-# [preg_replace, pattern, replacement] - Perform a regular expression replace
-#     using the preg_replace() PHP function.  If you use backreferences in your
-#     replacement phrase, be sure to escape dollar signs (i.e. \$1, not $1).
-# [uppercase] - Convert string to uppercase
-#
-# See the CallNumber search below for an example of custom munging in action.
-#-----------------------------------------------------------------------------------
-
-# These searches use Dismax when possible:
-Author:
-  DismaxParams:
-    - [bf , ord(publishDateSort)^10]
-  DismaxFields:
-    - author^400
-    - author2^300
-    - author_id^100
-    - author_ref^150
-    - author_corporate^200
-    - author_corporate2^200
-    - author_orig^200
-    - author2_orig^200
-    - author_corporate_orig^200
-    - author_corporate2_orig^200
-    - author_fuller^50
-    - author2_fuller
-    - author_additional
-    - author_variant
-    - author2_variant
-  QueryFields:
-    author:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author_fuller:
-      - [onephrase, 200]
-      - [and, 100]
-      - [or, 50]
-    author2:
-      - [onephrase, 100]
-      - [and, 50]
-      - [or, ~]
-    author_ref:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author_corporate:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author_corporate2:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author_orig:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author2_orig:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author_corporate_orig:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author_corporate2_orig:
-      - [onephrase, 350]
-      - [and, 200]
-      - [or, 100]
-    author_id:
-      - [onephrase, 450]
-      - [and, 300]
-      - [or, 200]
-    author2_fuller:
-      - [onephrase, 100]
-      - [and, 50]
-      - [or, ~]
-    author_additional:
-      - [onephrase, 100]
-      - [and, 50]
-      - [or, ~]
-    author_variant:
-      - [onephrase, 100]
-      - [and, 50]
-      - [or, ~]
-    author2_variant:
-      - [onephrase, 100]
-      - [and, 50]
-      - [or, ~]
-
-ISN:
-  DismaxFields:
-    - isbn
-    - issn
-    - ismn
-  QueryFields:
-    issn:
-      - [and, 100]
-      - [or, ~]
-    isbn:
-      - [and, 100]
-      - [or, ~]
-    ismn:
-      - [and, 100]
-      - [or, ~]
-
-Signatur:
-#  DismaxParams:
-#    - [mm, 0]
-#  DismaxFields:
-#    - callnumber_ISIL
-  QueryFields:
-    callnumber_ISIL:
-      - [onephrase, 1000]
-      - [and, 100]
-      - [or, ~]
-
-Barcode:
-#  DismaxParams:
-#    - [mm, 0]
-#  DismaxFields:
-#    - barcode_ISIL
-  QueryFields:
-    barcode_ISIL:
-      - [onephrase, 1000]
-      - [and, 100]
-      - [or, ~]
-
-Subject:
-  DismaxFields:
-    - topic_unstemmed^150
-    - topic^100
-    - topic_id^100
-    - topic_ref^100
-    #- geographic^50
-    #- genre^50
-    #- era
-  QueryFields:
-    topic_unstemmed:
-      - [onephrase, 350]
-      - [and, 150]
-      - [or, ~]
-    topic:
-      - [onephrase, 300]
-      - [and, 100]
-      - [or, ~]
-    topic_ref:
-      - [onephrase, 100]
-      - [and, 50]
-      - [or, ~]
-    topic_id:
-      - [onephrase, 100]
-      - [and, 50]
-      - [or, ~]
-    #- geographic:
-    #  - [onephrase, 300]
-    #  - [and, 100]
-    #  - [or, ~]
-    #- genre:
-    #  - [onephrase, 300]
-    #  - [and, 100]
-    #  - [or, ~]
-    #- era:
-    #  - [and, 100]
-    #  - [or, ~]
-#  ExactSettings:
-#    DismaxFields:
-#      - topic_unstemmed^150
-#    QueryFields:
-#      - topic_unstemmed:
-#        - [onephrase, 350]
-#        - [and, 150]
-#        - [or, ~]
-
-# This field definition is a compromise that supports both journal-level and
-# article-level data.  The disadvantage is that hits in article titles will
-# be mixed in.  If you are building a purely article-oriented index, you should
-# customize this to remove all of the title_* fields and focus entirely on the
-# container_title field.
-JournalTitle:
-  DismaxFields:
-    - title_short^500
-    - title_full_unstemmed^450
-    - title_full^400
-    - title^300
-    - container_title^250
-    - title_alt^200
-    - title_new^100
-    - title_old
-    - series^100
-    - series2
-  QueryFields:
-    title_short:
-      - [onephrase, 500]
-    title_full_unstemmed:
-      - [onephrase, 450]
-      - [and, 400]
-    title_full:
-      - [onephrase, 400]
-    title:
-      - [onephrase, 300]
-      - [and, 250]
-    container_title:
-      - [onephrase, 275]
-      - [and, 225]
-    title_alt:
-      - [and, 200]
-    title_new:
-      - [and, 100]
-    title_old:
-      - [and, ~]  
-    series:
-      - [onephrase, 100]
-      - [and, 50]
-    series2:
-      - [onephrase, 50]
-      - [and , ~]
-  FilterQuery: "format:Journal OR format:Article OR format:ElectronicBookPart"
-#  ExactSettings:
-#    DismaxFields:
-#      - title_full_unstemmed^450
-#    QueryFields:
-#      - title_full_unstemmed:
-#        - [onephrase, 450]
-#        - [and, 400]
-#    FilterQuery: "format:Journal OR format:Article"
-
-Title:
-  DismaxParams:
-    - [mm, 3]
-    - [bf , ord(publishDateSort)^10]
-  DismaxFields:
-#    - title_sub^200
-#    - title_short^300
-    - title_full_unstemmed^150
-    - title_full^100
-    - title^900
-    - title_alt^200
-    - title_new^100
-    - title_old
-    - title_orig^400
-    - series^100
-    - series2
-    - series_orig^100
-  QueryFields:
-    title_short:
-      - [onephrase, 500]
-    title_full_unstemmed:
-      - [onephrase, 150]
-      - [and, 100]
-    title_full:
-      - [onephrase, 100]
-    title:
-      - [onephrase, 300]
-      - [and, 250]
-    title_alt:
-      - [and, 200]
-    title_new:
-      - [and, 100]
-    title_old:
-      - [and, ~]
-    title_orig:
-      - [onephrase, 500]
-      - [and, 200]
-    series:
-      - [onephrase, 100]
-      - [and, 50]
-    series2:
-      - [onephrase, 50]
-      - [and , ~]
-    series_orig:
-      - [onephrase, 100]
-      - [and, 50]
-#  ExactSettings:
-#    DismaxFields:
-#      - title_full_unstemmed^450
-#    QueryFields:
-#      - title_full_unstemmed:
-#        - [onephrase, 450]
-#        - [and, 400]
-
-Series:
-  DismaxFields:
-    - series^100
-    - series2
-    - series_orig^100
-  QueryFields:
-    series:
-      - [onephrase, 500]
-      - [and, 200]
-      - [or, 100]
-    series2:
-      - [onephrase, 50]
-      - [and, 50]
-      - [or, ~]
-    series_orig:
-      - [onephrase, 500]
-      - [and, 200]
-      - [or, 100]
-
-Series2:
-  DismaxFields:
-    - series2
-  QueryFields:
-    series2:
-      - [onephrase, 200]
-      - [and, 50]
-
-AllFields:
-  DismaxParams:
-    - [mm, 3]
-    - [bf , ord(publishDateSort)^10]
-#    - [bf , "if(exists(query({!v='source_id:0'})),10,1)^1000"]
-    - [bf, "if(exists(query({!v='access_facet:Local*'})),10,1)^1000"]
-  DismaxFields:
-    - title_short^1000
-    - title_full_unstemmed^1000
-    - title_full^400
-    - title^500
-    - title_alt^200
-    - title_new^100
-    - title_orig^500
-    - series^50
-    - series2^30
-    - series_orig^50
-    - author^500
-    - author_fuller^150
-    - author_corporate^300
-    - author2^400
-    - author_corporate2^100
-    - author_ref^500
-    - author_orig^300
-    - author2_orig^300
-    - author_corporate_orig^300
-    - author_corporate2_orig^100
-    - topic_ref^10
-    - contents^10
-    - topic_unstemmed^15
-    - topic^10
-    - geographic^10
-    - genre^10
-    - rvk_label
-    - allfields_unstemmed^10
-    - allfields
-    - fulltext
-    - isbn
-    - issn
-    - ismn
-
-  QueryFields:
-    0:
-      0:
-        - OR
-        - 50
-      title_short:
-        - [onephrase, 1000]
-      title_full_unstemmed:
-        - [onephrase, 1000]
-        - [and, 500]
-      title_full:
-        - [onephrase, 400]
-      title:
-        - [onephrase, 300]
-        - [and, 250]
-      title_alt:
-        - [and, 200]
-      title_new:
-        - [and, 100]
-      title_orig:
-        - [onephrase, 500]
-        - [and, 400]
-    series:
-      - [onephrase, 300]
-      - [and, 100]
-    series2:
-      - [and, 30]
-    series_orig:
-      - [onephrase, 200]
-      - [and, 100]
-    author:
-      - [onephrase, 500]
-      - [and, 250]
-    author_fuller:
-      - [onephrase, 150]
-      - [and, 125]
-    author_ref:
-      - [onephrase, 250]
-      - [and, 250]
-      - [or, 250]
-    author_orig:
-      - [onephrase, 500]
-      - [and, 250]
-    author2_orig:
-      - [and, 50]
-    author_corporate_orig:
-      - [onephrase, 500]
-      - [and, 400]
-    author_corporate2_orig:
-      - [and, 50]
-    author_corporate:
-      - [onephrase, 500]
-      - [and, 400]
-    author2:
-      - [and, 50]
-    author_additional:
-      - [and, 50]
-    author_corporate2:
-      - [and, 50]
-    contents:
-      - [and, 10]
-    topic_unstemmed:
-      - [onephrase, 55]
-      - [and, 50]
-    topic:
-      - [onephrase, 50]
-    topic_ref:
-      - [onephrase, 10]
-      - [and, 5]
-      - [or, 5]
-    topic_id:
-      - [onephrase, 50]
-      - [and, 25]
-    allfields_unstemmed:
-      - [or, 10]
-#    fulltext_unstemmed:
-#      - [or, 10]
-    allfields:
-      - [or, ~]
-    fulltext:
-      - [or, ~]
-#    description:
-#      - [or, ~]
-    rvk_label:
-      - [onephrase, 500]
-      - [and, 250]
-      - [or, 250]
-    isbn:
-      - [onephrase, 500]
-    issn:
-      - [onephrase, 500]
-    ismn:
-      - [onephrase, 500]
-    imprint:
-      - [onephrase, 500]
-      
-#  ExactSettings:
-#    DismaxFields:
-#      - title_full_unstemmed^600
-#      - topic_unstemmed^550
-#      - allfields_unstemmed^10
-#      - fulltext_unstemmed^10
-#      - isbn
-#      - issn
-#    QueryFields:
-#      title_full_unstemmed:
-#        - [onephrase, 600]
-#        - [and, 500]
-#      topic_unstemmed:
-#        - [onephrase, 550]
-#        - [and, 500]
-#      allfields_unstemmed:
-#        - [or, 10]
-#      fulltext_unstemmed:
-#        - [or, 10]
-#      isbn:
-#        - [onephrase, ~]
-#      issn:
-#        - [onephrase, ~]
-
-# These are advanced searches that never use Dismax:
-id:
-  QueryFields:
-    id:
-      - [onephrase, ~]
-
-ParentID:
-  QueryFields:
-    hierarchy_parent_id:
-      - [onephrase, ~]
-
-# Fields for exact matches originating from alphabetic browse
-ids:
-  QueryFields:
-    id:
-      - [or, ~]
-
-TopicBrowse:
-  QueryFields:
-    topic_browse:
-      - [onephrase, ~]
-
-AuthorBrowse:
-  QueryFields:
-    author_browse:
-      - [onephrase, ~]
-
-TitleBrowse:
-  QueryFields:
-    title_full:
-      - [onephrase, ~]
-
-DeweyBrowse:
-  QueryFields:
-    dewey-raw:
-      - [onephrase, ~]
-
-LccBrowse:
-  QueryFields:
-    callnumber-a:
-      - [onephrase, ~]
-
-
-
-# CallNumber:
-  # We use two similar munges here -- one for exact matches, which will get
-  # a very high boost factor, and one for left-anchored wildcard searches,
-  # which will return a larger number of hits at a lower boost.
-  #CustomMunge:
-    #callnumber_exact:
-      #- [uppercase]
-      # Strip whitespace and quotes:
-      #- [preg_replace, '/[ "]/', '']
-      # Escape colons (unescape first to avoid double-escapes):
-      #- [preg_replace, '/(\\\:)/', ':']
-      #- [preg_replace, '/:/', '\:']
-      # Strip pre-existing trailing asterisks:
-      #- [preg_replace, '/\*+$/', '']
-    #callnumber_fuzzy:
-      #- [uppercase]
-      # Strip whitespace and quotes:
-      #- [preg_replace, '/[ "]/', '']
-      # Escape colons (unescape first to avoid double-escapes):
-      #- [preg_replace, '/(\\\:)/', ':']
-      #- [preg_replace, '/:/', '\:']
-      # Strip pre-existing trailing asterisks, then add a new one:
-      #- [preg_replace, '/\*+$/', '']
-      #- [append, "*"]
-  QueryFields:
-    callnumber-search:
-      - [callnumber_exact, 1000]
-      - [callnumber_fuzzy, ~]
-    dewey-search:
-      - [callnumber_exact, 1000]
-      - [callnumber_fuzzy, ~]
-
-publisher:
-  DismaxFields:
-    - publisher^100
-  QueryFields:
-    publisher:
-      - [and, 100]
-      - [or, ~]
-
-year:
-  DismaxFields:
-    - publishDate^100
-  QueryFields:
-    publishDate:
-      - [and, 100]
-      - [or, ~]
-
-language:
-  QueryFields:
-    language:
-      - [and, ~]
-
-toc:
-  DismaxFields:
-    - contents^100
-  QueryFields:
-    contents:
-      - [and, 100]
-      - [or, ~]
-
-topic:
-  QueryFields:
-    topic:
-      - [and, 50]
-    topic_facet:
-      - [and, ~]
-
-geographic:
-  QueryFields:
-    geographic:
-      - [and, 50]
-    geographic_facet:
-      - [and, ~]
-
-genre:
-  QueryFields:
-    genre:
-      - [and, 50]
-    genre_facet:
-      - [and, ~]
-
-era:
-  QueryFields:
-    era:
-      - [and, ~]
-
-oclc_num:
-  CustomMunge:
-    oclc_num:
-      - [preg_replace, "/[^0-9]/", ""]
-      # trim leading zeroes:
-      - [preg_replace, "/^0*/", ""]
-  QueryFields:
-    oclc_num:
-      - [oclc_num, ~]
-      
-rvk:
-  DismaxFields:
-    - rvk_facet^100
-  QueryFields:
-    rvk_facet:
-      - [and, 50]
-      - [or, 50]
-      
-rvk_path:
-  QueryFields:
-    rvk_path:
-      - [onephrase, ~]
-
-multipart:
-  DismaxFields:
-    - multipart_link^100
-  QueryFields:
-    multipart_link:
-      - [and, 50]
-      - [or, 50]  
-
-titleUniform:
-  QueryFields:
-    title_id_str_mv:
-      - [onephrase, ~]
\ No newline at end of file
+"@parent_yaml": "../../../config/vufind/searchspecs.yaml"
\ No newline at end of file
diff --git a/local/config/vufind/searches.ini b/local/config/vufind/searches.ini
index f6d6e5cdecfcffb5dea73ac3ea3062502d7bed2a..1755366b266c17057da838ee76691cda9bb653ed 100644
--- a/local/config/vufind/searches.ini
+++ b/local/config/vufind/searches.ini
@@ -98,11 +98,6 @@ retain_filters_by_default = true
 ;default_filters[] = "institution:MyInstitution"
 ;default_filters[] = "(format:Book AND institution:MyInstitution)"
 
-; the escaped_colon_searches is used by a listener on the search-pre event
-; registered by the MungerInjectionFactory. This listener masks colons in the query string with a backslash
-; whenever the search handler is one of the following
-escaped_colon_searches[] = "Signatur"
-
 [Cache]
 ; This controls whether the parsed searchspecs.yaml file will be stored to
 ; improve search performance; legal options are APC (use APC cache), File (store
@@ -677,3 +672,13 @@ height = 320
 ;params = "qf=title,title_short,callnumber-label,topic,language,author,publishDate mintf=1 mindf=1";
 ; This setting can be used to limit the maximum number of suggestions. Default is 5.
 ;count = 5
+
+; PreMunge is used by a listener on the search-pre event
+; registered by the MungerInjectionDelegatorFactory. This listener applies
+; the regex patterns as configured for the search handlers
+; use the following configuration to define pattern and replacement as used in @see preg_replace
+; Handler[pattern] = regex_pattern with delimiters (e.g. slashes at beginning and end)
+; Handler[replace] = replacement
+[PreMunge]
+Signatur[pattern] = '/(?<=\:)\s/'
+Signatur[replace] = '\ '
\ No newline at end of file
diff --git a/local/config/vufind/searchspecs.yaml b/local/config/vufind/searchspecs.yaml
index 04693c444c6f66c5caf3995f329d85a1e26c05da..74bfe85f84c160244469c9db2744ce53ca7ea7c8 100644
--- a/local/config/vufind/searchspecs.yaml
+++ b/local/config/vufind/searchspecs.yaml
@@ -150,6 +150,8 @@
 # See the CallNumber search below for an example of custom munging in action.
 #-----------------------------------------------------------------------------------
 
+"@parent_yaml": "../../../config/vufind/searchspecs.yaml"
+
 # These searches use Dismax when possible:
 Author:
   DismaxParams:
@@ -352,7 +354,7 @@ JournalTitle:
     title_new:
       - [and, 100]
     title_old:
-      - [and, ~]  
+      - [and, ~]
     series:
       - [onephrase, 100]
       - [and, 50]
@@ -455,7 +457,7 @@ AllFields:
     - [mm, 3]
     - [bf , ord(publishDateSort)^10]
 #    - [bf , "if(exists(query({!v='source_id:0'})),10,1)^1000"]
-    - [bf, "if(exists(query({!v='access_facet:Local*'})),10,1)^1000"]
+    - [bf, "if(exists(query({!v='facet_avail:Local*'})),10,1)^1000"]
   DismaxFields:
     - title_short^1000
     - title_full_unstemmed^1000
@@ -611,147 +613,12 @@ AllFields:
 #      issn:
 #        - [onephrase, ~]
 
-# These are advanced searches that never use Dismax:
-id:
-  QueryFields:
-    id:
-      - [onephrase, ~]
-
-ParentID:
-  QueryFields:
-    hierarchy_parent_id:
-      - [onephrase, ~]
-
-# Fields for exact matches originating from alphabetic browse
-ids:
-  QueryFields:
-    id:
-      - [or, ~]
-
-TopicBrowse:
-  QueryFields:
-    topic_browse:
-      - [onephrase, ~]
-
-AuthorBrowse:
-  QueryFields:
-    author_browse:
-      - [onephrase, ~]
-
-TitleBrowse:
-  QueryFields:
-    title_full:
-      - [onephrase, ~]
-
-DeweyBrowse:
-  QueryFields:
-    dewey-raw:
-      - [onephrase, ~]
-
+# These are advanced searches that never use Dismax (finc customized):
 LccBrowse:
   QueryFields:
     callnumber-a:
       - [onephrase, ~]
 
-
-
-# CallNumber:
-  # We use two similar munges here -- one for exact matches, which will get
-  # a very high boost factor, and one for left-anchored wildcard searches,
-  # which will return a larger number of hits at a lower boost.
-  #CustomMunge:
-    #callnumber_exact:
-      #- [uppercase]
-      # Strip whitespace and quotes:
-      #- [preg_replace, '/[ "]/', '']
-      # Escape colons (unescape first to avoid double-escapes):
-      #- [preg_replace, '/(\\\:)/', ':']
-      #- [preg_replace, '/:/', '\:']
-      # Strip pre-existing trailing asterisks:
-      #- [preg_replace, '/\*+$/', '']
-    #callnumber_fuzzy:
-      #- [uppercase]
-      # Strip whitespace and quotes:
-      #- [preg_replace, '/[ "]/', '']
-      # Escape colons (unescape first to avoid double-escapes):
-      #- [preg_replace, '/(\\\:)/', ':']
-      #- [preg_replace, '/:/', '\:']
-      # Strip pre-existing trailing asterisks, then add a new one:
-      #- [preg_replace, '/\*+$/', '']
-      #- [append, "*"]
-  #QueryFields:
-    #callnumber-search:
-      #- [callnumber_exact, 1000]
-      #- [callnumber_fuzzy, ~]
-    #dewey-search:
-      #- [callnumber_exact, 1000]
-      #- [callnumber_fuzzy, ~]
-
-publisher:
-  DismaxFields:
-    - publisher^100
-  QueryFields:
-    publisher:
-      - [and, 100]
-      - [or, ~]
-
-year:
-  DismaxFields:
-    - publishDate^100
-  QueryFields:
-    publishDate:
-      - [and, 100]
-      - [or, ~]
-
-language:
-  QueryFields:
-    language:
-      - [and, ~]
-
-toc:
-  DismaxFields:
-    - contents^100
-  QueryFields:
-    contents:
-      - [and, 100]
-      - [or, ~]
-
-topic:
-  QueryFields:
-    topic:
-      - [and, 50]
-    topic_facet:
-      - [and, ~]
-
-geographic:
-  QueryFields:
-    geographic:
-      - [and, 50]
-    geographic_facet:
-      - [and, ~]
-
-genre:
-  QueryFields:
-    genre:
-      - [and, 50]
-    genre_facet:
-      - [and, ~]
-
-era:
-  QueryFields:
-    era:
-      - [and, ~]
-
-oclc_num:
-  CustomMunge:
-    oclc_num:
-      - [preg_replace, "/[^0-9]/", ""]
-      # trim leading zeroes:
-      - [preg_replace, "/^0*/", ""]
-  QueryFields:
-    oclc_num:
-      - [oclc_num, ~]
-      
 rvk:
   DismaxFields:
     - rvk_facet^100
diff --git a/local/languages/de.ini b/local/languages/de.ini
index 6ccede79bbadd7e71fe3d3af92a8cacbde920a62..33b2bf08b837d996024c0eed35d1684c547ef0bc 100644
--- a/local/languages/de.ini
+++ b/local/languages/de.ini
@@ -1020,7 +1020,7 @@ DE-1972 = "Robert Schumann Hochschule Düsseldorf"
 DE-Ch1 = "Technische Universität Chemnitz"
 DE-D13 = "Staatliche Kunstsammlungen Dresden"
 DE-D40 = "Staatliche Ethnographische Sammlungen Sachsen, Museum für Völkerkunde Dresden"
-DE-D117 = "Hochschule für Musik - Carl Maria von Weber - Dresden"
+DE-D117 = "Hochschule für Musik 'Carl Maria von Weber' Dresden"
 DE-Mit1 = "Hochschule Mittweida"
 DE-L152 = "Hochschule für Musik und Theater 'Felix Mendelssohn Bartholdy' Leipzig"
 DE-L189 = "Hochschule für Technik, Wirtschaft und Kultur Leipzig"
@@ -1029,7 +1029,6 @@ DE-L242 = "Hochschule für Grafik und Buchkunst Leipzig"
 DE-L328 = "Halle 14 Kunstbibliothek Leipzig"
 DE-L330 = "GfZK Leipzig"
 DE-Kn38 = "Bibliothek der HfMT Köln"
-DE-Wim8 = "Hochschule für Musik 'Franz Liszt' Weimar"
 DE-Zi4 = "Hochschule Zittau/Görlitz"
 DE-Zwi2 = "Westsächsische Hochschule Zwickau"
 Dresden SLUB = "Sächsische Landesbibliothek & Staats- und Universitätsbibliothek Dresden (SLUB)"
diff --git a/local/languages/en.ini b/local/languages/en.ini
index 67e9b0c1a6e40a47dbfa7ea4cb3390a5745ffbd5..2bb335bd894b27c953b63fc72515b61c5125260b 100644
--- a/local/languages/en.ini
+++ b/local/languages/en.ini
@@ -980,7 +980,7 @@ DE-1972 = "Robert Schumann Academy Düsseldorf"
 DE-Ch1 = "Technische Universität Chemnitz"
 DE-D13 = "The Staatliche Kunstsammlungen Dresden (Dresden State Art Collections) "
 DE-D40 = "Staatliche Ethnographische Sammlungen Sachsen, Museum für Völkerkunde Dresden"
-DE-D117 = "Hochschule für Musik - Carl Maria von Weber - Dresden"
+DE-D117 = "Hochschule für Musik 'Carl Maria von Weber' Dresden"
 DE-Mit1 = "Hochschule Mittweida"
 DE-L152 = "University of Music and Theatre 'Felix Mendelssohn Bartholdy' Leipzig"
 DE-L189 = "Leipzig University of Applied Sciences"
@@ -989,7 +989,6 @@ DE-L242 = "Academy of Visual Arts Leipzig"
 DE-L328 = "Halle 14 Art Library Leipzig"
 DE-L330 = "GfZK Leipzig"
 DE-Kn38 = "Library of HfMT Köln"
-DE-Wim8 = "The Liszt School of Music Weimar"
 DE-Zi4 = "Zittau/Görlitz University of Applied Sciences"
 DE-Zwi2 = "University of Applied Sciences Zwickau"
 ;Dresden SLUB = "Sächsische Landesbibliothek & Staats- und Universitätsbibliothek Dresden (SLUB)"
diff --git a/module/finc/src/finc/AjaxHandler/GetAdditionalAccountInfo.php b/module/finc/src/finc/AjaxHandler/GetAdditionalAccountInfo.php
index 96a7ca446394680744c9a89ddf866096a8cf2003..5d6f7c179bac46e2e9a432520de03eca80c3b484 100644
--- a/module/finc/src/finc/AjaxHandler/GetAdditionalAccountInfo.php
+++ b/module/finc/src/finc/AjaxHandler/GetAdditionalAccountInfo.php
@@ -61,16 +61,16 @@ class GetAdditionalAccountInfo extends \VuFind\AjaxHandler\AbstractIlsAndUserAct
         try {
             $patron = $this->ilsAuthenticator->storedCatalogLogin();
             if ($patron) {
-                $additionalAccountInfos = [];
-                $additionalAccountInfos['countViewItems'] = $this->ils->countItems(
+                $countViewItems = $this->ils->countItems(
                     $viewsToCount,
                     $patron
                 );
 
-                $additionalAccountInfos['countFines'] = $this->ils->getFinesTotal(
+                $countFines = $this->ils->getFinesTotal(
                     $patron
                 );
-                return $this->formatResponse(compact('additionalAccountInfos'));
+
+                return $this->formatResponse(compact('countViewItems','countFines'));
             }
         } catch (\Exception $e) {
             // Do nothing -- just fail through to the error message below.
diff --git a/module/finc/src/finc/Controller/MyResearchController.php b/module/finc/src/finc/Controller/MyResearchController.php
index a48c096b571e7f6777c69da7c13b225b2f246bac..ff311d42b26feb415d8d54625b3d9da985717ae6 100644
--- a/module/finc/src/finc/Controller/MyResearchController.php
+++ b/module/finc/src/finc/Controller/MyResearchController.php
@@ -26,9 +26,11 @@
  * @link     https://vufind.org Main Site
  */
 namespace finc\Controller;
-use Zend\Log\LoggerAwareInterface as LoggerAwareInterface,
-    Zend\Mvc\MvcEvent as MvcEvent;
+
+use VuFind\Search\RecommendListener;
+use Zend\Log\LoggerAwareInterface as LoggerAwareInterface;
 use Zend\Mvc\Controller\Plugin\Url;
+use Zend\Mvc\MvcEvent as MvcEvent;
 
 /**
  * Controller for the user account area.
@@ -62,10 +64,131 @@ class MyResearchController extends \VuFind\Controller\MyResearchController imple
             //we achieve that by forcing the follow-up url to be the redirect
             //but watch out, the redirect via followup will only work if the redirect url
             //is schemed correctly
-            if (!is_string($redirect)) $this->flashMessenger()->addErrorMessage('Invalid data type for redirect');
-            elseif (!preg_match('/^https?\:\/\//',$redirect)) $this->flashMessenger()->addErrorMessage('Invalid Redirect: '.$redirect);
-            else $this->followup()->store(['finc-redirect' => $redirect]);
+            if (!is_string($redirect)) {
+                $this->flashMessenger()->addErrorMessage('Invalid data type for redirect');
+            } elseif (!preg_match('/^https?\:\/\//', $redirect)) {
+                $this->flashMessenger()->addErrorMessage('Invalid Redirect: ' . $redirect);
+            } else {
+                $this->followup()->store(['finc-redirect' => $redirect]);
+            }
         }
         return parent::onDispatch($e);
     }
+
+    /**
+     * Send user's saved favorites from a particular list to the view
+     *
+     * @return mixed
+     */
+    public function mylistAction()
+    {
+        // Fail if lists are disabled:
+        if (!$this->listsEnabled()) {
+            throw new ForbiddenException('Lists disabled');
+        }
+
+        // Check for "delete item" request; parameter may be in GET or POST depending
+        // on calling context.
+        $deleteId = $this->params()->fromPost(
+            'delete', $this->params()->fromQuery('delete')
+        );
+        if ($deleteId) {
+            $deleteSource = $this->params()->fromPost(
+                'source',
+                $this->params()->fromQuery('source', DEFAULT_SEARCH_BACKEND)
+            );
+            // If the user already confirmed the operation, perform the delete now;
+            // otherwise prompt for confirmation:
+            $confirm = $this->params()->fromPost(
+                'confirm', $this->params()->fromQuery('confirm')
+            );
+            if ($confirm) {
+                $layout = $this->params()->fromPost(
+                    'layout', $this->params()->fromQuery('layout')
+                );
+
+                /* #17712 not necessary to fetch items after deleting by ajax */
+                $success = $this->performDeleteFavorite($deleteId, $deleteSource);
+                if ($layout === 'lightbox' || $success !== true) {
+                    return $success;
+                }
+
+            } else {
+                return $this->confirmDeleteFavorite($deleteId, $deleteSource);
+            }
+        }
+
+        // If we got this far, we just need to display the favorites:
+        try {
+            $runner = $this->serviceLocator->get('VuFind\Search\SearchRunner');
+
+            // We want to merge together GET, POST and route parameters to
+            // initialize our search object:
+            $request = $this->getRequest()->getQuery()->toArray()
+                + $this->getRequest()->getPost()->toArray()
+                + ['id' => $this->params()->fromRoute('id')];
+
+            // Set up listener for recommendations:
+            $rManager = $this->serviceLocator
+                ->get('VuFind\Recommend\PluginManager');
+            $setupCallback = function ($runner, $params, $searchId) use ($rManager) {
+                $listener = new RecommendListener($rManager, $searchId);
+                $listener->setConfig(
+                    $params->getOptions()->getRecommendationSettings()
+                );
+                $listener->attach($runner->getEventManager()->getSharedManager());
+            };
+
+            $results = $runner->run($request, 'Favorites', $setupCallback);
+            return $this->createViewModel(
+                ['params' => $results->getParams(), 'results' => $results]
+            );
+        } catch (ListPermissionException $e) {
+            if (!$this->getUser()) {
+                return $this->forceLogin();
+            }
+            throw $e;
+        }
+    }
+
+    /**
+     * Delete record
+     * Overrides Vufind method without flash messages
+     *
+     * @param string $id     ID of record to delete
+     * @param string $source Source of record to delete
+     *
+     * @return bool|mixed
+     *
+     * @throws \Exception
+     **/
+    public function performDeleteFavorite($id, $source)
+    {
+        // Force login:
+        $user = $this->getUser();
+        if (!$user) {
+            return $this->forceLogin();
+        }
+
+        // Load/check incoming parameters:
+        $listID = $this->params()->fromRoute('id');
+        $listID = empty($listID) ? null : $listID;
+        if (empty($id)) {
+            throw new \Exception('Cannot delete empty ID!');
+        }
+
+        // Perform delete #17712 without setting messages
+        if (null !== $listID) {
+            // ...Specific List
+            $table = $this->getTable('UserList');
+            $list = $table->getExisting($listID);
+            $list->removeResourcesById($user, [$id], $source);
+        } else {
+            // ...My Favorites
+            $user->removeResourcesById([$id], $source);
+        }
+
+        // All done -- return true to indicate success.
+        return true;
+    }
 }
diff --git a/module/finc/src/finc/Hierarchy/TreeDataFormatter/NoCollections.php b/module/finc/src/finc/Hierarchy/TreeDataFormatter/NoCollections.php
index 5d1fbf8a3538b07a70a246d491df917b057a3d0b..f3a54bc14215f7a6d3cefff826321261fe2330d2 100644
--- a/module/finc/src/finc/Hierarchy/TreeDataFormatter/NoCollections.php
+++ b/module/finc/src/finc/Hierarchy/TreeDataFormatter/NoCollections.php
@@ -64,4 +64,29 @@ class NoCollections extends \VuFind\Hierarchy\TreeDataFormatter\Json
         }
         return parent::pickTitle($record, $parentID);
     }
+
+
+    /**
+     * Sort Nodes
+     * Convert an unsorted array of [ key, value ] pairs into a sorted array
+     * of values.
+     *
+     * @param array $array The array of arrays to sort
+     *
+     * @return array
+     */
+    protected function sortNodes($array)
+    {
+        // Sort arrays based on first element
+        $sorter = function ($a, $b) {
+            return strnatcmp($a[0], $b[0]);
+        };
+        usort($array, $sorter);
+
+        // Collapse array to remove sort values
+        $mapper = function ($i) {
+            return $i[1];
+        };
+        return array_map($mapper, $array);
+    }
 }
diff --git a/module/finc/src/finc/ILS/Driver/FincLibero.php b/module/finc/src/finc/ILS/Driver/FincLibero.php
index f2325e0b0158612d0157af798c1ca1adc290bfad..3448b5bb62a77bd84c8fbf06ac983086bb4d8831 100644
--- a/module/finc/src/finc/ILS/Driver/FincLibero.php
+++ b/module/finc/src/finc/ILS/Driver/FincLibero.php
@@ -691,4 +691,14 @@ class FincLibero extends FincILS implements TranslatorAwareInterface
         return $pickUpLocations;
     }
 
+    /**
+     * TODO: check status of this function
+     * de_15 -> getBoundItemId() vs. de_l152 -> getBoundItemInfo()
+     * @param $item
+     * @return array
+     */
+    protected function getBoundItemId($item)
+    {
+        return [];
+    }
 }
diff --git a/module/finc/src/finc/ILS/Driver/PAIA.php b/module/finc/src/finc/ILS/Driver/PAIA.php
index 579991694b2ae73935a3f6e7c6ee52e7ce069054..6eb349d54e3b6137e2f748bb063a95cb8fe64c1f 100644
--- a/module/finc/src/finc/ILS/Driver/PAIA.php
+++ b/module/finc/src/finc/ILS/Driver/PAIA.php
@@ -71,6 +71,11 @@ class PAIA extends \VuFind\ILS\Driver\PAIA
 
     protected $notificationsPrefix;
 
+    /**
+     * @var array
+     */
+    protected $conditions;
+
     /**
      * Constructor
      *
@@ -97,6 +102,10 @@ class PAIA extends \VuFind\ILS\Driver\PAIA
         if (isset($this->config['PAIA']['paiaNotificationsPrefix'])) {
             $this->notificationsPrefix = $this->config['PAIA']['paiaNotificationsPrefix'];
         }
+
+        if (isset($this->config['PAIA']['paiaConditions'])) {
+            $this->conditions = $this->config['PAIA']['paiaConditions'];
+        }
     }
 
     /**
@@ -972,6 +981,9 @@ class PAIA extends \VuFind\ILS\Driver\PAIA
             $result['upc'] = null;
             */
 
+            /* #17508 read optional PAIA information like pickupbranch */
+            $this->mapOptions($doc, $result);
+
             $results[] = $result;
         }
         return $results;
@@ -1511,4 +1523,66 @@ class PAIA extends \VuFind\ILS\Driver\PAIA
         // return TRUE on success
         return true;
     }
+
+    /***
+     * finds conditions in PAIA items array and adds it as options in finc converted items array
+     *
+     * Conditions according to PAIA default are only intended for request, renew or cancel
+     * BUT here used for items too, therefore should be only one option per condition
+     * see: http://gbv.github.io/paia/paia.html#conditions-and-confirmations
+     *
+     * @param array         $doc  Array of PAIA input to be mapped
+     * @param array         $result Array of PAIA output - called by reference
+     * @param array|null    $configConditions conditions to be considered (optional, if null use config)
+     *
+     * @return bool True if any option was found, otherwise false.
+     */
+    protected function mapOptions(array $doc, array &$result, array $configConditions = null): bool
+    {
+        $configConditions = $configConditions ?? $this->conditions ?? [];
+
+        foreach ($configConditions as $configCondition) {
+            if ($optionsValues = $doc["condition"][$configCondition]["option"] ?? []) {
+                $found = $result["options"][$configCondition] = $optionsValues;
+            }
+        }
+
+        return !empty($found);
+    }
+
+    /**
+     * Get (first) option for given condition and valid options from finc converted PAIA result
+     *
+     * @param string    $condition
+     * @param array     $result
+     * @param boolean   $onlyFirst
+     * @param array     $validOptions
+     *
+     * @return array of option values: each either valid id or label or null if none set
+     */
+    public function getOptions(string $condition, array $result, bool $onlyFirst, array $validOptions = null): array
+    {
+        $retval = [];
+        if (in_array($condition, $this->conditions)) {
+            $options = $result['options'][$condition] ?? [];
+            foreach ($options as $option) {
+                if (!$validOptions) {
+                    $retval[] = $option['about'] ?? $option['id'] ?? null;
+                    if ($onlyFirst) {
+                        break;
+                    }
+                } else {
+                    foreach ($validOptions as $validOption) {
+                        if (array_intersect($validOption, $option)) {
+                            $retval[] = $option['id'] ?? $option['about'] ?? null;
+                            if ($onlyFirst) {
+                                break 2;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return $retval;
+    }
 }
diff --git a/module/finc/src/finc/RecordDriver/SolrMarc.php b/module/finc/src/finc/RecordDriver/SolrMarc.php
index cc56769933055587c5f099eae4ac05955f508ab2..fb29e876891f8680c6d61ae52edc790219b03fd4 100644
--- a/module/finc/src/finc/RecordDriver/SolrMarc.php
+++ b/module/finc/src/finc/RecordDriver/SolrMarc.php
@@ -506,13 +506,27 @@ class SolrMarc extends SolrDefault
         $toc = [];
         foreach ($fields as $field) {
             $subfields = $field->getSubfields();
-            foreach ($subfields as $subfield) {
-                // Break the string into appropriate chunks, filtering empty strings,
-                // and merge them into return array:
-                $toc = array_merge(
-                    $toc,
-                    array_filter(explode('--', $subfield->getData()), 'trim')
-                );
+            $ind2 = $field->getIndicator(2);
+            if ($ind2 === '0') {
+                // advanced entries with
+                // $t - Title
+                // $r - Statement of responsibility
+                $tocSubfields = [];
+                foreach ($subfields as $subfield) {
+                    $tocSubfields[] = $subfield->getData();
+                }
+                $toc[] = implode(' \ ', $tocSubfields);
+            } elseif ($ind2 === '#') {
+                // basic entries
+                // sub-entries delimited by " -- "
+                foreach ($subfields as $subfield) {
+                    // Break the string into appropriate chunks, filtering empty strings,
+                    // and merge them into return array:
+                    $toc = array_merge(
+                        $toc,
+                        array_filter(explode('--', $subfield->getData()), 'trim')
+                    );
+                }
             }
         }
         return $toc;
diff --git a/module/finc/src/finc/RecordDriver/SolrMarcFinc.php b/module/finc/src/finc/RecordDriver/SolrMarcFinc.php
index c5de93123f0f84059613535a4ec5f7bf88876dd2..d9e2976d91d20ef81f53c057574bd1dd186131f9 100644
--- a/module/finc/src/finc/RecordDriver/SolrMarcFinc.php
+++ b/module/finc/src/finc/RecordDriver/SolrMarcFinc.php
@@ -57,7 +57,7 @@ class SolrMarcFinc extends SolrMarc
     /**
      * pattern to identify normed data set of GND
      */
-    const GND_PATTERN = '/^(\(DE-588\))(\d+)(\w|)/';
+    const GND_PATTERN = '/^(\(DE-588\))(\d+)(\S*)$/';
 
     /**
      * List of isil of institution
diff --git a/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php b/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php
index a8e7c73152d7e29db3d4d3dc5a85757aa709e8b7..88a0c6846b618f6f94a4fdfebb2c037e637dbab6 100644
--- a/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php
+++ b/module/finc/src/finc/RecordDriver/SolrMarcFincTrait.php
@@ -86,7 +86,8 @@ trait SolrMarcFincTrait
         // Which fields/subfields should we check for URLs?
         $fieldsToCheck = [
             '856' => ['u'],   // Standard URL
-            '555' => ['a']         // Cumulative index/finding aids
+            '555' => ['a'],         // Cumulative index/finding aids
+            '609' => ['a']
         ];
 
         foreach ($fieldsToCheck as $field => $subfields) {
@@ -115,18 +116,43 @@ trait SolrMarcFincTrait
                         if ($address) {
                             $address = $address->getData();
 
-                            $tmpArr = [];
+                            //$tmpArr = [];
                             // Is there a description?  If not, just use the URL
                             // itself.
-                            foreach (['y', '3', 'z', 'x'] as $current) {
+                            /*foreach (['y', '3', 'z', 'x'] as $current) {
                                 $desc = $url->getSubfield($current);
                                 if ($desc) {
                                     $desc = $desc->getData();
                                     $tmpArr[] = $desc;
                                 }
+                            }*/
+
+                            $desc = false;
+                            if ($url->getSubfield('y')) {
+                                $desc = $url->getSubfield('y');
+                            } elseif ($url->getSubfield('n')) {
+                                $desc = $url->getSubfield('n');
+                            } elseif ($url->getSubfield('3')) {
+                                $desc = $url->getSubfield('3');
                             }
-                            $tmpArr = array_unique($tmpArr);
-                            $desc = implode(', ', $tmpArr);
+
+
+                            if ($desc) {
+                                $desc = $desc->getData();
+                                $desc = strlen($desc) > 3 ?
+                                    $desc : 'Online Information';
+
+                                if (isset($this->mainConfig->UrlDesc->desc)) {
+                                    $tmpDesc = $this->mainConfig->UrlDesc->desc->toArray();
+                                    if (in_array($desc, $tmpDesc)) {
+                                        $desc = 'Online Information';
+                                    }
+                                }
+                            } else {
+                                $desc = 'Online Information';
+                            }
+                            //$tmpArr = array_unique($tmpArr);
+                            //$desc = implode(', ', $tmpArr);
 
                             // If no description take url as description
                             // For 856[40] url denoting resource itself
@@ -149,16 +175,55 @@ trait SolrMarcFincTrait
                                 $retVal
                             )
                             ) {
-                                $retVal[] = ['url' => $address, 'desc' => $desc];
+                                $retVal[] = [
+                                    'url' => $address, 'desc' => $desc,
+                                    'indicators' => $indicator1.$indicator2
+                                ];
                             }
                         }
                     }
+                    if ($field == '609') {
+                        // 609 cpntains Gallica (BNF) info. Cf. #10279. Included here via #15042
+                        $data = $url->getSubfield('a')->getData();
+                        if (stripos($data, 'http') === 0) {
+                            $retVal[] = ['url' => $data, 'desc' => '', 'indicators' => '40'];
+                        }
+                    }
                 }
             }
         }
         return $retVal;
     }
 
+
+    /**
+     * Return a description about volumes in stock via consortial field
+     * with subfield $h.
+     * Currently only used in DE-L152.
+     *
+     * @return array
+     * @access public
+     */
+    public function getVolumesInStock()
+    {
+        $volumesInStock = $this->getFieldArray($this->getLocalMarcFieldOfLibrary(), ['h']);
+        return array_filter($volumesInStock);
+    }
+
+    /**
+     * Return a user relevant comment about volumes in stock via consortial field
+     * with subfield $i.
+     * Currently only used in DE-L152.
+     *
+     * @return array
+     * @access public
+     */
+    public function getVolumesCommentInStock()
+    {
+        $volumesCommentInStock = $this->getFieldArray($this->getLocalMarcFieldOfLibrary(), ['i']);
+        return array_filter($volumesCommentInStock);
+    }
+
     /**
      * Checks if the record is an EBL record (as defined in config.ini section
      * [Ebl]->product_sigel). Refs #8055 #9634
@@ -766,6 +831,28 @@ trait SolrMarcFincTrait
         return $retval;
     }
 
+    /**
+     * Get additional entry meeting or jurisdiction names.
+     *
+     * @return array $retval
+     * @link   https://intern.finc.info/issues/9369
+     */
+    public function getAddedEntryMeetingNames()
+    {
+        $retval = [];
+
+        $fields = $this->getMarcRecord()->getFields('711');
+        if (!$fields) {
+            return [];
+        }
+        foreach ($fields as $key => $field) {
+            if ($q = $field->getSubfield('a')) {
+                $retval[$key] = $q->getData();
+            }
+        }
+        return $retval;
+    }
+
     /**
      * Get all local class subjects. First realization for HGB. Refs #2626
      *
diff --git a/module/finc/src/finc/Role/PermissionProvider/IpRangeFoFor.php b/module/finc/src/finc/Role/PermissionProvider/IpRangeFoFor.php
index 134d1a0dad6f9a6697eeb6f36ff4217bb1188263..37106e439210a6b3eae83e5838085b769c1fbf15 100644
--- a/module/finc/src/finc/Role/PermissionProvider/IpRangeFoFor.php
+++ b/module/finc/src/finc/Role/PermissionProvider/IpRangeFoFor.php
@@ -45,6 +45,28 @@ namespace finc\Role\PermissionProvider;
  */
 class IpRangeFoFor extends \VuFind\Role\PermissionProvider\IpRange
 {
+
+    /**
+     * returns remote address based on eventual proxy headers
+     * 
+     * @return string
+     */
+    private function getRemoteAddr() {
+        // a list of ips the request is forwarded for - first is latest
+        $HttpXForwardedForList = explode(',', $this->request->getServer()->get('HTTP_X_FORWARDED_FOR'));
+        
+        if ($ip = array_shift($HttpXForwardedForList)) {
+            return $ip;
+        }
+        
+        // often provided by nginx-reverse-proxies, should be used since its the nature of the value
+        if ($ip = $this->request->getServer()->get('HTTP_X_REAL_IP')) {
+            return $ip;
+        }
+
+        return $this->request->getServer()->get('REMOTE_ADDR');
+    }
+
     /**
      * Return an array of roles which may be granted the permission based on
      * the options.
@@ -57,11 +79,10 @@ class IpRangeFoFor extends \VuFind\Role\PermissionProvider\IpRange
      */
     public function getPermissions($options)
     {
+
+
         // Check if any regex matches....
-        $ip = $this->request->getServer()->get('HTTP_X_FORWARDED_FOR') != null
-            ? $this->request->getServer()->get('HTTP_X_FORWARDED_FOR')
-            : $this->request->getServer()->get('REMOTE_ADDR');
-        if ($this->ipAddressUtils->isInRange($ip, (array)$options)) {
+        if ($this->ipAddressUtils->isInRange($this->getRemoteAddr(), (array)$options)) {
             // Match? Grant to all users (guest or logged in).
             return ['guest', 'loggedin'];
         }
diff --git a/module/finc/src/finc/Role/PermissionProvider/IpRegExFoFor.php b/module/finc/src/finc/Role/PermissionProvider/IpRegExFoFor.php
index 07afb30d8f1717f736087d265ef6876b677b042d..c54b27513668296471c0f9843f86463f15f772ac 100644
--- a/module/finc/src/finc/Role/PermissionProvider/IpRegExFoFor.php
+++ b/module/finc/src/finc/Role/PermissionProvider/IpRegExFoFor.php
@@ -22,6 +22,7 @@
  * @category VuFind
  * @package  Authorization
  * @author   Gregor Gawol <gawol@ub.uni-leipzig.de>
+ * @author   Ulf Seltmann <ulf.seltmann@hmt-leipzig.de>
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org Main Page
  */
@@ -33,11 +34,33 @@ namespace finc\Role\PermissionProvider;
  * @category VuFind
  * @package  Authorization
  * @author   Gregor Gawol <gawol@ub.uni-leipzig.de>
+ * @author   Ulf Seltmann <ulf.seltmann@hmt-leipzig.de>
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org Main Page
  */
 class IpRegExFoFor extends \VuFind\Role\PermissionProvider\IpRegEx
 {
+    /**
+     * returns remote address based on eventual proxy headers
+     * 
+     * @return string
+     */
+    private function getRemoteAddr() {
+        // a list of ips the request is forwarded for - first is latest
+        $HttpXForwardedForList = explode(',', $this->request->getServer()->get('HTTP_X_FORWARDED_FOR'));
+        
+        if ($ip = array_shift($HttpXForwardedForList)) {
+            return $ip;
+        }
+        
+        // often provided by nginx-reverse-proxies, should be used since its the nature of the value
+        if ($ip = $this->request->getServer()->get('HTTP_X_REAL_IP')) {
+            return $ip;
+        }
+
+        return $this->request->getServer()->get('REMOTE_ADDR');
+    }
+
     /**
      * Return an array of roles which may be granted the permission based on
      * the options.
@@ -51,9 +74,7 @@ class IpRegExFoFor extends \VuFind\Role\PermissionProvider\IpRegEx
     public function getPermissions($options)
     {
         // Check if any regex matches....
-        $ip = $this->request->getServer()->get('HTTP_X_FORWARDED_FOR') != null
-            ? $this->request->getServer()->get('HTTP_X_FORWARDED_FOR')
-            : $this->request->getServer()->get('REMOTE_ADDR');
+        $ip = $this->getRemoteAddr();
         foreach ((array)$options as $current) {
             if (preg_match($current, $ip)) {
                 // Match? Grant to all users (guest or logged in).
diff --git a/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php b/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php
index 1d65c7b2af5bdcd2a310a71e820c74c1c86059a4..9bc29e20a830a8464dd35d3ca4f6f96f97e3d59c 100644
--- a/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php
+++ b/module/finc/src/finc/Service/MungerInjectionDelegatorFactory.php
@@ -56,9 +56,9 @@ class MungerInjectionDelegatorFactory implements DelegatorFactoryInterface
     protected $instance;
 
     /**
-     * @var array names of search handlers for which colons should be escaped
+     * @var array configuration for pre-search munging
      */
-    protected $searches_to_escape;
+    protected $preMungerConfig;
 
     /**
      * @var array shard configuration to register in all queries
@@ -86,16 +86,14 @@ class MungerInjectionDelegatorFactory implements DelegatorFactoryInterface
         $searchConfig = $container->get('VuFind\Config')->get('searches');
         $e = $instance->getEventManager()->getSharedManager();
 
-        $handlers = $searchConfig->General->escaped_colon_searches;
-        if (!empty($handlers)) {
-            $this->searches_to_escape = $handlers->toArray();
+        if ($this->validateMungerConfig($searchConfig)) {
             $e->attach(
                 'VuFindSearch',
                 'pre',
                 function (EventInterface $event) {
                     $params = $event->getParams();
                     if (isset($params['query'])) {
-                        $params['query'] = $this->escapeColons($params['query']);
+                        $params['query'] = $this->preMunge($params['query']);
                     }
                 }
             );
@@ -120,26 +118,53 @@ class MungerInjectionDelegatorFactory implements DelegatorFactoryInterface
      *
      * @return mixed
      */
-    private function escapeColons($queryOrGroup)
+    private function preMunge($queryOrGroup)
     {
         if ($queryOrGroup instanceof QueryGroup) {
             $handler = $queryOrGroup->getReducedHandler();
-            if (is_null($handler) || in_array($handler, $this->searches_to_escape)) {
+            if (is_null($handler) || isset($this->preMungerConfig[$handler])) {
                 foreach ($queryOrGroup->getQueries() as $query) {
-                    $this->escapeColons($query);
+                    $this->preMunge($query);
                 }
             }
-        } elseif (in_array($queryOrGroup->getHandler(), $this->searches_to_escape)) {
+        } elseif ($mungerConfig = $this->preMungerConfig[$queryOrGroup->getHandler()] ?? null) {
             $queryOrGroup->setString(
-                // mask whitespaces that follow a colon
-                // that avoids the removal of that very colon via
-                // \VuFindSearch\Backend\Solr\LuceneSyntaxHelper::normalizeColons
-                preg_replace('/(?<=\:)\s/', '\ ', $queryOrGroup->getString())
+                // apply preg_replace as provided by config
+                preg_replace($mungerConfig['pattern'], $mungerConfig['replace'], $queryOrGroup->getString())
             );
         }
         return $queryOrGroup;
     }
 
+    /**
+     * @param Config $searchConfig
+     */
+    protected function validateMungerConfig($searchConfig) {
+
+        $config = $searchConfig->PreMunge;
+        if (empty($config)) {
+            // an empty configuration is always valid
+            return null;
+        }
+        $preMungerConfig = $config->toArray();
+        foreach ($preMungerConfig as $handler => $handlerConfig) {
+            if (!isset($handlerConfig['pattern'])
+                ||
+                !isset($handlerConfig['replace'])
+            ) {
+                throw new \ConfigurationException("PreMunge configuration for $handler is invalid");
+            }
+            try {
+                // try preg_replace so the regex engine tells us about more errors
+                preg_replace($handlerConfig['pattern'], $handlerConfig['replace'], '');
+            } catch (\ErrorException $e) {
+                throw new \ConfigurationException("PreMunge configuration for $handler is invalid. Regex error: ".'"'.$e->getMessage().'"');
+            }
+        }
+        $this->preMungerConfig = $preMungerConfig;
+        return true;
+    }
+
     /**
      * Event Listener on Search/Pre that registers all configured shards for every
      * search request
diff --git a/module/finc/src/finc/View/Helper/Root/MultiDataFieldsTrait.php b/module/finc/src/finc/View/Helper/Root/MultiDataFieldsTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..be5bd3f42ad61d9ce894e31b91c12c8514082404
--- /dev/null
+++ b/module/finc/src/finc/View/Helper/Root/MultiDataFieldsTrait.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * multi data view helper callback functions
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Leipzig University Library, 2020.
+ *
+ * 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  View_Helpers
+ * @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
+ */
+namespace finc\View\Helper\Root;
+
+/**
+ * multi data view helper callback functions
+ *
+ * @category VuFind
+ * @package  View_Helpers
+ * @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
+ */
+trait MultiDataFieldsTrait
+{
+    /**
+     * return render details for additionals data set
+     * @return callback
+     */
+    public function additionals($data, $options)
+    {
+        // Sort the data:
+        $final = [];
+        foreach ($data as $type => $values) {
+            $final[] = [
+                'label' => $values['identifier'],
+                'values' => [$type => $values],
+                'options' => [
+                    'pos' => $options['pos'],
+                    'renderType' => 'RecordDriverTemplate',
+                    'template' => 'data-additionals.phtml',
+                ],
+            ];
+        }
+        return $final;
+    }
+
+    /**
+     * return render details for otherRelationshipEntry data set
+     * @return callback
+     */
+    public function otherRelationShipEntry($data, $options)
+    {
+        // Sort the data:
+        $final = [];
+        foreach ($data as $type => $values) {
+            $final[] = [
+                'label' => $values[0]['subject'],
+                'values' => [$type => $values],
+                'options' => [
+                    'pos' => $options['pos'],
+                    'renderType' => 'RecordDriverTemplate',
+                    'template' => 'data-otherRelationshipEntry.phtml',
+                ],
+            ];
+        }
+        return $final;
+    }
+
+    /**
+     * return render details for events data set
+     * @return callback
+     */
+    public function events($data, $options)
+    {
+        // Sort the data:
+        $final = [];
+        foreach ($data as $eventType => $values) {
+            switch ($eventType) {
+                case 'production': $title = 'expression creation'; break;
+                case 'publication': $title = 'Time of origin'; break;
+                default: $title = $eventType;
+            }
+            $final[] = [
+                'label' => $title,
+                'values' => [$eventType => $values],
+                'options' => [
+                    'pos' => $options['pos'],
+                    'renderType' => 'RecordDriverTemplate',
+                    'template' => 'data-events.phtml',
+                ],
+            ];
+        }
+        return $final;
+    }
+
+
+    /**
+     * return render details for titleUniform data set
+     * @return callback
+     */
+    public function isbnIssns($data, $options, $driver)
+    {
+        // Sort the data:
+        $final = [];
+        foreach ($data as $type => $values) {
+            $final[] = [
+                'label' => $driver->tryMethod('isRDA')
+                    ? 'rda_original_title'
+                    : 'non_rda_original_title',
+                'values' => [$type => $values],
+                'options' => [
+                    'pos' => $options['pos'],
+                    'renderType' => 'RecordDriverTemplate',
+                    'template' => 'data-titleUniform.phtml',
+                ],
+            ];
+        }
+        return $final;
+    }
+
+}
diff --git a/module/finc/src/finc/View/Helper/Root/Record.php b/module/finc/src/finc/View/Helper/Root/Record.php
index df2d737400ed10d2d944ba7ed146c19003daf30b..5a59179a709d672563e851cd8cc01eae53ce517b 100644
--- a/module/finc/src/finc/View/Helper/Root/Record.php
+++ b/module/finc/src/finc/View/Helper/Root/Record.php
@@ -309,7 +309,7 @@ class Record extends \VuFind\View\Helper\Root\Record
             $link = $this->rewriteLink($link);
         }
 
-        return $links;
+        return array_filter($links);
     }
 
     /**
diff --git a/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php b/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php
index 2867335c86c34d4991a0a90204679d2cbba00c34..5d3672c0b4b64a7e4af23578902dbd4bcd4838a3 100644
--- a/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php
+++ b/module/finc/src/finc/View/Helper/Root/RecordDataFormatterFactory.php
@@ -364,9 +364,8 @@ class RecordDataFormatterFactory
             'getTitleUniform',
             'data-titleUniform.phtml',
             [
-                'labelFunction' => function () {
-                    return null;
-                }
+                'labelFunction' =>
+                    [$this,'titleUniformLabel']
             ]
         );
         $spec->setLine(
@@ -853,9 +852,8 @@ class RecordDataFormatterFactory
             'getTitleUniform',
             'data-titleUniform.phtml',
             [
-                'labelFunction' => function () {
-                    return null;
-                }
+                'labelFunction' =>
+                    [$this,'titleUniformLabel']
             ]
         );
         $spec->setLine(
@@ -949,4 +947,18 @@ class RecordDataFormatterFactory
         $spec->setTemplateLine('Tags', true, 'data-tags.phtml');
         return $spec->getArray();
     }
+
+    /**
+     * Labels titleUniform Data Line based on the record
+     * being catalogued following RDA, or not. cf. https://projekte.ub.uni-leipzig.de/issues/13830
+     * @param $data
+     * @param $driver
+     * @return string
+     */
+    public function titleUniformLabel($data, $driver)
+    {
+        return $driver->tryMethod('isRDA')
+            ? 'rda_original_title'
+            : 'non_rda_original_title';
+    }
 }
diff --git a/module/finc/tests/bootstrap.php b/module/finc/tests/bootstrap.php
index a03458275c1d520c2a3d245360b0c8fdfe9699d9..62190b89ea090fd0f18525a3a470d51dd26ce0c0 100644
--- a/module/finc/tests/bootstrap.php
+++ b/module/finc/tests/bootstrap.php
@@ -101,7 +101,8 @@ class Bootstrap
         ];
         self::initEnvironment();
         $config = ArrayUtils::merge($baseConfig, $testConfig);
-        $serviceManager = new ServiceManager(new ServiceManagerConfig());
+        $managerConfig = new ServiceManagerConfig();
+        $serviceManager = new ServiceManager($managerConfig->toArray());
         $serviceManager->setService('ApplicationConfig', $config);
         $serviceManager->get('ModuleManager')->loadModules();
         static::$serviceManager = $serviceManager;
@@ -113,7 +114,7 @@ class Bootstrap
      *
      * @return void
      */
-    public function initEnvironment()
+    public static function initEnvironment()
     {
         define('APPLICATION_ENV', 'development');
         define('FINC_TEST_FIXTURES', realpath(FINC_TESTS_PATH . '/fixtures'));
diff --git a/module/finc/tests/fixtures/configs/yaml/searchspecs/config/vufind/searchspecs.yaml b/module/finc/tests/fixtures/configs/yaml/searchspecs/config/vufind/searchspecs.yaml
new file mode 120000
index 0000000000000000000000000000000000000000..183f7ab71ef5f14bbcc59307e6b8dca43de50bea
--- /dev/null
+++ b/module/finc/tests/fixtures/configs/yaml/searchspecs/config/vufind/searchspecs.yaml
@@ -0,0 +1 @@
+../../../../../../../../../config/vufind/searchspecs.yaml
\ No newline at end of file
diff --git a/module/finc/tests/fixtures/configs/yaml/searchspecs/local/alpha/config/vufind/searchspecs.yaml b/module/finc/tests/fixtures/configs/yaml/searchspecs/local/alpha/config/vufind/searchspecs.yaml
new file mode 120000
index 0000000000000000000000000000000000000000..59ae9378e2e3c4bd888febe0d254c83cae12fd47
--- /dev/null
+++ b/module/finc/tests/fixtures/configs/yaml/searchspecs/local/alpha/config/vufind/searchspecs.yaml
@@ -0,0 +1 @@
+../../../../../../../../../../../local/alpha/config/vufind/searchspecs.yaml
\ No newline at end of file
diff --git a/module/finc/tests/fixtures/configs/yaml/searchspecs/local/config/vufind/searchspecs.yaml b/module/finc/tests/fixtures/configs/yaml/searchspecs/local/config/vufind/searchspecs.yaml
new file mode 120000
index 0000000000000000000000000000000000000000..74dc0578aea75218966a506946443c45ff22c962
--- /dev/null
+++ b/module/finc/tests/fixtures/configs/yaml/searchspecs/local/config/vufind/searchspecs.yaml
@@ -0,0 +1 @@
+../../../../../../../../../../local/config/vufind/searchspecs.yaml
\ No newline at end of file
diff --git a/module/finc/tests/fixtures/configs/yaml/searchspecs/result/local/alpha/searchspecs.yaml b/module/finc/tests/fixtures/configs/yaml/searchspecs/result/local/alpha/searchspecs.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1e8bfdcad8d618d13d088011c0a10904df2be051
--- /dev/null
+++ b/module/finc/tests/fixtures/configs/yaml/searchspecs/result/local/alpha/searchspecs.yaml
@@ -0,0 +1,501 @@
+Author:
+  DismaxParams:
+    -
+      - bf
+      - ord(publishDateSort)^10
+  DismaxFields:
+    - author^400
+    - author2^300
+    - author_id^100
+    - author_ref^150
+    - author_corporate^200
+    - author_corporate2^200
+    - author_orig^200
+    - author2_orig^200
+    - author_corporate_orig^200
+    - author_corporate2_orig^200
+    - author_fuller^50
+    - author2_fuller
+    - author_additional
+    - author_variant
+    - author2_variant
+  QueryFields:
+    author:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_fuller:
+      - [onephrase, 200]
+      - [and, 100]
+      - [or, 50]
+    author2:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author_ref:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate2:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author2_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate2_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_id:
+      - [onephrase, 450]
+      - [and, 300]
+      - [or, 200]
+    author2_fuller:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author_additional:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author_variant:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author2_variant:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+ISN:
+  DismaxFields:
+    - isbn
+    - issn
+    - ismn
+  QueryFields:
+    issn:
+      - [and, 100]
+      - [or, null]
+    isbn:
+      - [and, 100]
+      - [or, null]
+    ismn:
+      - [and, 100]
+      - [or, null]
+Signatur:
+  QueryFields:
+    callnumber_ISIL:
+      - [onephrase, 1000]
+      - [and, 100]
+      - [or, null]
+Barcode:
+  QueryFields:
+    barcode_ISIL:
+      - [onephrase, 1000]
+      - [and, 100]
+      - [or, null]
+Subject:
+  DismaxFields:
+    - topic_unstemmed^150
+    - topic^100
+    - topic_id^100
+    - topic_ref^100
+  QueryFields:
+    topic_unstemmed:
+      - [onephrase, 350]
+      - [and, 150]
+      - [or, null]
+    topic:
+      - [onephrase, 300]
+      - [and, 100]
+      - [or, null]
+    topic_ref:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    topic_id:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+JournalTitle:
+  DismaxFields:
+    - title_short^500
+    - title_full_unstemmed^450
+    - title_full^400
+    - title^300
+    - container_title^250
+    - title_alt^200
+    - title_new^100
+    - title_old
+    - series^100
+    - series2
+  QueryFields:
+    title_short:
+      - [onephrase, 500]
+    title_full_unstemmed:
+      - [onephrase, 450]
+      - [and, 400]
+    title_full:
+      - [onephrase, 400]
+    title:
+      - [onephrase, 300]
+      - [and, 250]
+    container_title:
+      - [onephrase, 275]
+      - [and, 225]
+    title_alt:
+      - [and, 200]
+    title_new:
+      - [and, 100]
+    title_old:
+      - [and, null]
+    series:
+      - [onephrase, 100]
+      - [and, 50]
+    series2:
+      - [onephrase, 50]
+      - [and, null]
+  FilterQuery: 'format:Journal OR format:Article OR format:ElectronicBookPart'
+Title:
+  DismaxParams:
+    -
+      - mm
+      - 3
+    -
+      - bf
+      - ord(publishDateSort)^10
+  DismaxFields:
+    - title_full_unstemmed^150
+    - title_full^100
+    - title^900
+    - title_alt^200
+    - title_new^100
+    - title_old
+    - title_orig^400
+    - series^100
+    - series2
+    - series_orig^100
+  QueryFields:
+    title_short:
+      - [onephrase, 500]
+    title_full_unstemmed:
+      - [onephrase, 150]
+      - [and, 100]
+    title_full:
+      - [onephrase, 100]
+    title:
+      - [onephrase, 300]
+      - [and, 250]
+    title_alt:
+      - [and, 200]
+    title_new:
+      - [and, 100]
+    title_old:
+      - [and, null]
+    title_orig:
+      - [onephrase, 500]
+      - [and, 200]
+    series:
+      - [onephrase, 100]
+      - [and, 50]
+    series2:
+      - [onephrase, 50]
+      - [and, null]
+    series_orig:
+      - [onephrase, 100]
+      - [and, 50]
+Series:
+  DismaxFields:
+    - series^100
+    - series2
+    - series_orig^100
+  QueryFields:
+    series:
+      - [onephrase, 500]
+      - [and, 200]
+      - [or, 100]
+    series2:
+      - [onephrase, 50]
+      - [and, 50]
+      - [or, null]
+    series_orig:
+      - [onephrase, 500]
+      - [and, 200]
+      - [or, 100]
+Series2:
+  DismaxFields:
+    - series2
+  QueryFields:
+    series2:
+      - [onephrase, 200]
+      - [and, 50]
+AllFields:
+  DismaxParams:
+    -
+      - mm
+      - 3
+    -
+      - bf
+      - ord(publishDateSort)^10
+    -
+      - bf
+      - 'if(exists(query({!v=''facet_avail:Local*''})),10,1)^1000'
+  DismaxFields:
+    - title_short^1000
+    - title_full_unstemmed^1000
+    - title_full^400
+    - title^500
+    - title_alt^200
+    - title_new^100
+    - title_orig^500
+    - series^50
+    - series2^30
+    - series_orig^50
+    - author^500
+    - author_fuller^150
+    - author_corporate^300
+    - author2^400
+    - author_corporate2^100
+    - author_ref^500
+    - author_orig^300
+    - author2_orig^300
+    - author_corporate_orig^300
+    - author_corporate2_orig^100
+    - topic_ref^10
+    - contents^10
+    - topic_unstemmed^15
+    - topic^10
+    - geographic^10
+    - genre^10
+    - rvk_label
+    - allfields_unstemmed^10
+    - allfields
+    - fulltext
+    - isbn
+    - issn
+    - ismn
+  QueryFields:
+    0:
+      0: [OR, 50]
+      title_short: [[onephrase, 1000]]
+      title_full_unstemmed: [[onephrase, 1000], [and, 500]]
+      title_full: [[onephrase, 400]]
+      title: [[onephrase, 300], [and, 250]]
+      title_alt: [[and, 200]]
+      title_new: [[and, 100]]
+      title_orig: [[onephrase, 500], [and, 400]]
+    series:
+      - [onephrase, 300]
+      - [and, 100]
+    series2:
+      - [and, 30]
+    series_orig:
+      - [onephrase, 200]
+      - [and, 100]
+    author:
+      - [onephrase, 500]
+      - [and, 250]
+    author_fuller:
+      - [onephrase, 150]
+      - [and, 125]
+    author_ref:
+      - [onephrase, 250]
+      - [and, 250]
+      - [or, 250]
+    author_orig:
+      - [onephrase, 500]
+      - [and, 250]
+    author2_orig:
+      - [and, 50]
+    author_corporate_orig:
+      - [onephrase, 500]
+      - [and, 400]
+    author_corporate2_orig:
+      - [and, 50]
+    author_corporate:
+      - [onephrase, 500]
+      - [and, 400]
+    author2:
+      - [and, 50]
+    author_additional:
+      - [and, 50]
+    author_corporate2:
+      - [and, 50]
+    contents:
+      - [and, 10]
+    topic_unstemmed:
+      - [onephrase, 55]
+      - [and, 50]
+    topic:
+      - [onephrase, 50]
+    topic_ref:
+      - [onephrase, 10]
+      - [and, 5]
+      - [or, 5]
+    topic_id:
+      - [onephrase, 50]
+      - [and, 25]
+    allfields_unstemmed:
+      - [or, 10]
+    allfields:
+      - [or, null]
+    fulltext:
+      - [or, null]
+    rvk_label:
+      - [onephrase, 500]
+      - [and, 250]
+      - [or, 250]
+    isbn:
+      - [onephrase, 500]
+    issn:
+      - [onephrase, 500]
+    ismn:
+      - [onephrase, 500]
+    imprint:
+      - [onephrase, 500]
+id:
+  QueryFields:
+    id:
+      - [onephrase, null]
+ParentID:
+  QueryFields:
+    hierarchy_parent_id:
+      - [onephrase, null]
+ids:
+  QueryFields:
+    id:
+      - [or, null]
+TopicBrowse:
+  QueryFields:
+    topic_browse:
+      - [onephrase, null]
+AuthorBrowse:
+  QueryFields:
+    author_browse:
+      - [onephrase, null]
+TitleBrowse:
+  QueryFields:
+    title_full:
+      - [onephrase, null]
+DeweyBrowse:
+  QueryFields:
+    dewey-raw:
+      - [onephrase, null]
+LccBrowse:
+  QueryFields:
+    callnumber-a:
+      - [onephrase, null]
+publisher:
+  DismaxFields:
+    - publisher^100
+  QueryFields:
+    publisher:
+      - [and, 100]
+      - [or, null]
+year:
+  DismaxFields:
+    - publishDate^100
+  QueryFields:
+    publishDate:
+      - [and, 100]
+      - [or, null]
+language:
+  QueryFields:
+    language:
+      - [and, null]
+toc:
+  DismaxFields:
+    - contents^100
+  QueryFields:
+    contents:
+      - [and, 100]
+      - [or, null]
+topic:
+  QueryFields:
+    topic:
+      - [and, 50]
+    topic_facet:
+      - [and, null]
+geographic:
+  QueryFields:
+    geographic:
+      - [and, 50]
+    geographic_facet:
+      - [and, null]
+genre:
+  QueryFields:
+    genre:
+      - [and, 50]
+    genre_facet:
+      - [and, null]
+era:
+  QueryFields:
+    era:
+      - [and, null]
+oclc_num:
+  CustomMunge:
+    oclc_num:
+      - [preg_replace, '/[^0-9]/', '']
+      - [preg_replace, '/^0*/', '']
+  QueryFields:
+    oclc_num:
+      - [oclc_num, null]
+rvk:
+  DismaxFields:
+    - rvk_facet^100
+  QueryFields:
+    rvk_facet:
+      - [and, 50]
+      - [or, 50]
+rvk_path:
+  QueryFields:
+    rvk_path:
+      - [onephrase, null]
+multipart:
+  DismaxFields:
+    - multipart_link^100
+  QueryFields:
+    multipart_link:
+      - [and, 50]
+      - [or, 50]
+titleUniform:
+  QueryFields:
+    title_id_str_mv:
+      - [onephrase, null]
+Coordinate:
+  DismaxFields:
+    - long_lat_display
+  DismaxHandler: edismax
+CallNumber:
+  CustomMunge:
+    callnumber_exact:
+      - [preg_replace, '/[ "]/', '']
+      - [preg_replace, '/(\\:)/', ':']
+      - [preg_replace, '/:/', '\:']
+      - [preg_replace, '/\*+$/', '']
+    callnumber_fuzzy:
+      - [preg_replace, '/[ "]/', '']
+      - [preg_replace, '/(\\:)/', ':']
+      - [preg_replace, '/:/', '\:']
+      - [preg_replace, '/\*+$/', '']
+      - [append, '*']
+  QueryFields:
+    callnumber-search:
+      - [callnumber_exact, 1000]
+      - [callnumber_fuzzy, null]
+    dewey-search:
+      - [callnumber_exact, 1000]
+      - [callnumber_fuzzy, null]
diff --git a/module/finc/tests/fixtures/configs/yaml/searchspecs/result/local/searchspecs.yaml b/module/finc/tests/fixtures/configs/yaml/searchspecs/result/local/searchspecs.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1e8bfdcad8d618d13d088011c0a10904df2be051
--- /dev/null
+++ b/module/finc/tests/fixtures/configs/yaml/searchspecs/result/local/searchspecs.yaml
@@ -0,0 +1,501 @@
+Author:
+  DismaxParams:
+    -
+      - bf
+      - ord(publishDateSort)^10
+  DismaxFields:
+    - author^400
+    - author2^300
+    - author_id^100
+    - author_ref^150
+    - author_corporate^200
+    - author_corporate2^200
+    - author_orig^200
+    - author2_orig^200
+    - author_corporate_orig^200
+    - author_corporate2_orig^200
+    - author_fuller^50
+    - author2_fuller
+    - author_additional
+    - author_variant
+    - author2_variant
+  QueryFields:
+    author:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_fuller:
+      - [onephrase, 200]
+      - [and, 100]
+      - [or, 50]
+    author2:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author_ref:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate2:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author2_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_corporate2_orig:
+      - [onephrase, 350]
+      - [and, 200]
+      - [or, 100]
+    author_id:
+      - [onephrase, 450]
+      - [and, 300]
+      - [or, 200]
+    author2_fuller:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author_additional:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author_variant:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    author2_variant:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+ISN:
+  DismaxFields:
+    - isbn
+    - issn
+    - ismn
+  QueryFields:
+    issn:
+      - [and, 100]
+      - [or, null]
+    isbn:
+      - [and, 100]
+      - [or, null]
+    ismn:
+      - [and, 100]
+      - [or, null]
+Signatur:
+  QueryFields:
+    callnumber_ISIL:
+      - [onephrase, 1000]
+      - [and, 100]
+      - [or, null]
+Barcode:
+  QueryFields:
+    barcode_ISIL:
+      - [onephrase, 1000]
+      - [and, 100]
+      - [or, null]
+Subject:
+  DismaxFields:
+    - topic_unstemmed^150
+    - topic^100
+    - topic_id^100
+    - topic_ref^100
+  QueryFields:
+    topic_unstemmed:
+      - [onephrase, 350]
+      - [and, 150]
+      - [or, null]
+    topic:
+      - [onephrase, 300]
+      - [and, 100]
+      - [or, null]
+    topic_ref:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+    topic_id:
+      - [onephrase, 100]
+      - [and, 50]
+      - [or, null]
+JournalTitle:
+  DismaxFields:
+    - title_short^500
+    - title_full_unstemmed^450
+    - title_full^400
+    - title^300
+    - container_title^250
+    - title_alt^200
+    - title_new^100
+    - title_old
+    - series^100
+    - series2
+  QueryFields:
+    title_short:
+      - [onephrase, 500]
+    title_full_unstemmed:
+      - [onephrase, 450]
+      - [and, 400]
+    title_full:
+      - [onephrase, 400]
+    title:
+      - [onephrase, 300]
+      - [and, 250]
+    container_title:
+      - [onephrase, 275]
+      - [and, 225]
+    title_alt:
+      - [and, 200]
+    title_new:
+      - [and, 100]
+    title_old:
+      - [and, null]
+    series:
+      - [onephrase, 100]
+      - [and, 50]
+    series2:
+      - [onephrase, 50]
+      - [and, null]
+  FilterQuery: 'format:Journal OR format:Article OR format:ElectronicBookPart'
+Title:
+  DismaxParams:
+    -
+      - mm
+      - 3
+    -
+      - bf
+      - ord(publishDateSort)^10
+  DismaxFields:
+    - title_full_unstemmed^150
+    - title_full^100
+    - title^900
+    - title_alt^200
+    - title_new^100
+    - title_old
+    - title_orig^400
+    - series^100
+    - series2
+    - series_orig^100
+  QueryFields:
+    title_short:
+      - [onephrase, 500]
+    title_full_unstemmed:
+      - [onephrase, 150]
+      - [and, 100]
+    title_full:
+      - [onephrase, 100]
+    title:
+      - [onephrase, 300]
+      - [and, 250]
+    title_alt:
+      - [and, 200]
+    title_new:
+      - [and, 100]
+    title_old:
+      - [and, null]
+    title_orig:
+      - [onephrase, 500]
+      - [and, 200]
+    series:
+      - [onephrase, 100]
+      - [and, 50]
+    series2:
+      - [onephrase, 50]
+      - [and, null]
+    series_orig:
+      - [onephrase, 100]
+      - [and, 50]
+Series:
+  DismaxFields:
+    - series^100
+    - series2
+    - series_orig^100
+  QueryFields:
+    series:
+      - [onephrase, 500]
+      - [and, 200]
+      - [or, 100]
+    series2:
+      - [onephrase, 50]
+      - [and, 50]
+      - [or, null]
+    series_orig:
+      - [onephrase, 500]
+      - [and, 200]
+      - [or, 100]
+Series2:
+  DismaxFields:
+    - series2
+  QueryFields:
+    series2:
+      - [onephrase, 200]
+      - [and, 50]
+AllFields:
+  DismaxParams:
+    -
+      - mm
+      - 3
+    -
+      - bf
+      - ord(publishDateSort)^10
+    -
+      - bf
+      - 'if(exists(query({!v=''facet_avail:Local*''})),10,1)^1000'
+  DismaxFields:
+    - title_short^1000
+    - title_full_unstemmed^1000
+    - title_full^400
+    - title^500
+    - title_alt^200
+    - title_new^100
+    - title_orig^500
+    - series^50
+    - series2^30
+    - series_orig^50
+    - author^500
+    - author_fuller^150
+    - author_corporate^300
+    - author2^400
+    - author_corporate2^100
+    - author_ref^500
+    - author_orig^300
+    - author2_orig^300
+    - author_corporate_orig^300
+    - author_corporate2_orig^100
+    - topic_ref^10
+    - contents^10
+    - topic_unstemmed^15
+    - topic^10
+    - geographic^10
+    - genre^10
+    - rvk_label
+    - allfields_unstemmed^10
+    - allfields
+    - fulltext
+    - isbn
+    - issn
+    - ismn
+  QueryFields:
+    0:
+      0: [OR, 50]
+      title_short: [[onephrase, 1000]]
+      title_full_unstemmed: [[onephrase, 1000], [and, 500]]
+      title_full: [[onephrase, 400]]
+      title: [[onephrase, 300], [and, 250]]
+      title_alt: [[and, 200]]
+      title_new: [[and, 100]]
+      title_orig: [[onephrase, 500], [and, 400]]
+    series:
+      - [onephrase, 300]
+      - [and, 100]
+    series2:
+      - [and, 30]
+    series_orig:
+      - [onephrase, 200]
+      - [and, 100]
+    author:
+      - [onephrase, 500]
+      - [and, 250]
+    author_fuller:
+      - [onephrase, 150]
+      - [and, 125]
+    author_ref:
+      - [onephrase, 250]
+      - [and, 250]
+      - [or, 250]
+    author_orig:
+      - [onephrase, 500]
+      - [and, 250]
+    author2_orig:
+      - [and, 50]
+    author_corporate_orig:
+      - [onephrase, 500]
+      - [and, 400]
+    author_corporate2_orig:
+      - [and, 50]
+    author_corporate:
+      - [onephrase, 500]
+      - [and, 400]
+    author2:
+      - [and, 50]
+    author_additional:
+      - [and, 50]
+    author_corporate2:
+      - [and, 50]
+    contents:
+      - [and, 10]
+    topic_unstemmed:
+      - [onephrase, 55]
+      - [and, 50]
+    topic:
+      - [onephrase, 50]
+    topic_ref:
+      - [onephrase, 10]
+      - [and, 5]
+      - [or, 5]
+    topic_id:
+      - [onephrase, 50]
+      - [and, 25]
+    allfields_unstemmed:
+      - [or, 10]
+    allfields:
+      - [or, null]
+    fulltext:
+      - [or, null]
+    rvk_label:
+      - [onephrase, 500]
+      - [and, 250]
+      - [or, 250]
+    isbn:
+      - [onephrase, 500]
+    issn:
+      - [onephrase, 500]
+    ismn:
+      - [onephrase, 500]
+    imprint:
+      - [onephrase, 500]
+id:
+  QueryFields:
+    id:
+      - [onephrase, null]
+ParentID:
+  QueryFields:
+    hierarchy_parent_id:
+      - [onephrase, null]
+ids:
+  QueryFields:
+    id:
+      - [or, null]
+TopicBrowse:
+  QueryFields:
+    topic_browse:
+      - [onephrase, null]
+AuthorBrowse:
+  QueryFields:
+    author_browse:
+      - [onephrase, null]
+TitleBrowse:
+  QueryFields:
+    title_full:
+      - [onephrase, null]
+DeweyBrowse:
+  QueryFields:
+    dewey-raw:
+      - [onephrase, null]
+LccBrowse:
+  QueryFields:
+    callnumber-a:
+      - [onephrase, null]
+publisher:
+  DismaxFields:
+    - publisher^100
+  QueryFields:
+    publisher:
+      - [and, 100]
+      - [or, null]
+year:
+  DismaxFields:
+    - publishDate^100
+  QueryFields:
+    publishDate:
+      - [and, 100]
+      - [or, null]
+language:
+  QueryFields:
+    language:
+      - [and, null]
+toc:
+  DismaxFields:
+    - contents^100
+  QueryFields:
+    contents:
+      - [and, 100]
+      - [or, null]
+topic:
+  QueryFields:
+    topic:
+      - [and, 50]
+    topic_facet:
+      - [and, null]
+geographic:
+  QueryFields:
+    geographic:
+      - [and, 50]
+    geographic_facet:
+      - [and, null]
+genre:
+  QueryFields:
+    genre:
+      - [and, 50]
+    genre_facet:
+      - [and, null]
+era:
+  QueryFields:
+    era:
+      - [and, null]
+oclc_num:
+  CustomMunge:
+    oclc_num:
+      - [preg_replace, '/[^0-9]/', '']
+      - [preg_replace, '/^0*/', '']
+  QueryFields:
+    oclc_num:
+      - [oclc_num, null]
+rvk:
+  DismaxFields:
+    - rvk_facet^100
+  QueryFields:
+    rvk_facet:
+      - [and, 50]
+      - [or, 50]
+rvk_path:
+  QueryFields:
+    rvk_path:
+      - [onephrase, null]
+multipart:
+  DismaxFields:
+    - multipart_link^100
+  QueryFields:
+    multipart_link:
+      - [and, 50]
+      - [or, 50]
+titleUniform:
+  QueryFields:
+    title_id_str_mv:
+      - [onephrase, null]
+Coordinate:
+  DismaxFields:
+    - long_lat_display
+  DismaxHandler: edismax
+CallNumber:
+  CustomMunge:
+    callnumber_exact:
+      - [preg_replace, '/[ "]/', '']
+      - [preg_replace, '/(\\:)/', ':']
+      - [preg_replace, '/:/', '\:']
+      - [preg_replace, '/\*+$/', '']
+    callnumber_fuzzy:
+      - [preg_replace, '/[ "]/', '']
+      - [preg_replace, '/(\\:)/', ':']
+      - [preg_replace, '/:/', '\:']
+      - [preg_replace, '/\*+$/', '']
+      - [append, '*']
+  QueryFields:
+    callnumber-search:
+      - [callnumber_exact, 1000]
+      - [callnumber_fuzzy, null]
+    dewey-search:
+      - [callnumber_exact, 1000]
+      - [callnumber_fuzzy, null]
diff --git a/module/finc/tests/fixtures/paia/response/fincWithAnotherPickupBranchOption.json b/module/finc/tests/fixtures/paia/response/fincWithAnotherPickupBranchOption.json
new file mode 100644
index 0000000000000000000000000000000000000000..75a8a368417b49ad518a215e0aea93df9a3d5734
--- /dev/null
+++ b/module/finc/tests/fixtures/paia/response/fincWithAnotherPickupBranchOption.json
@@ -0,0 +1,50 @@
+HTTP/1.1 200 OK
+Server: vzg-paia/2.2-RC3 (Wed Jun 01 17:38:24 CEST 2016)
+Date: Mon, 20 Jun 2020 10:38:21 GMT
+Cache-Control: no-cache
+Access-Control-Allow-Origin: *
+Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes
+Access-Control-Allow-Headers: Content-Type, Authorization
+Pragma: no-cache
+X-OAuth-Scopes: change_password read_items read_fees read_patron
+X-Accepted-OAuth-Scopes: read_patron
+Content-Type: application/json; charset=UTF-8
+
+{
+  "doc": [
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:03201978",
+      "about": "Urbanität als Habitus",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:80086919",
+      "about": "Fernleihe mit Abholstation",
+      "condition": {
+        "http://purl.org/ontology/paia#PickupCondition": {
+          "option": [
+            {
+              "id": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI",
+              "about": "Zweigbibliothek Zittau",
+              "amount": "2,50 EUR"
+            }
+        ]
+        }
+      },
+      "storage": "Hochschulbibliothek Görlitz",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR"
+    },
+    {
+      "status": 0,
+      "item": "ILL:13940",
+      "about": "Aushandlungen städtischer Größe - Eckert, Anna",
+      "label": "Medium im Vormerkregal",
+      "starttime": "2020-03-12T00:00:00+01:00"
+    }
+  ]
+}
diff --git a/module/finc/tests/fixtures/paia/response/fincWithMultiplePickupBranchOptions.json b/module/finc/tests/fixtures/paia/response/fincWithMultiplePickupBranchOptions.json
new file mode 100644
index 0000000000000000000000000000000000000000..19225a142cbce6509093f88ca72aa45642982717
--- /dev/null
+++ b/module/finc/tests/fixtures/paia/response/fincWithMultiplePickupBranchOptions.json
@@ -0,0 +1,53 @@
+HTTP/1.1 200 OK
+Server: vzg-paia/2.2-RC3 (Wed Jun 01 17:38:24 CEST 2016)
+Date: Mon, 20 Jun 2020 10:38:21 GMT
+Cache-Control: no-cache
+Access-Control-Allow-Origin: *
+Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes
+Access-Control-Allow-Headers: Content-Type, Authorization
+Pragma: no-cache
+X-OAuth-Scopes: change_password read_items read_fees read_patron
+X-Accepted-OAuth-Scopes: read_patron
+Content-Type: application/json; charset=UTF-8
+
+{
+  "doc": [
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:03201978",
+      "about": "Urbanität als Habitus",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:80086919",
+      "about": "Fernleihe mit Abholstation",
+      "condition": {
+        "http://purl.org/ontology/paia#PickupCondition": {
+          "option": [
+            {
+              "id": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI",
+              "about": "Zweigbibliothek Zittau"
+            },
+            {
+              "id": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR",
+              "about": "Zweigbibliothek Görlitz"
+            }
+        ]
+        }
+      },
+      "storage": "Hochschulbibliothek Görlitz",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR"
+    },
+    {
+      "status": 0,
+      "item": "ILL:13940",
+      "about": "Aushandlungen städtischer Größe - Eckert, Anna",
+      "label": "Medium im Vormerkregal",
+      "starttime": "2020-03-12T00:00:00+01:00"
+    }
+  ]
+}
diff --git a/module/finc/tests/fixtures/paia/response/fincWithOnePickupBranchOption.json b/module/finc/tests/fixtures/paia/response/fincWithOnePickupBranchOption.json
new file mode 100644
index 0000000000000000000000000000000000000000..c00ed7217e75f197d4c4f79841eadf4f2b45b291
--- /dev/null
+++ b/module/finc/tests/fixtures/paia/response/fincWithOnePickupBranchOption.json
@@ -0,0 +1,66 @@
+HTTP/1.1 200 OK
+Server: vzg-paia/2.2-RC3 (Wed Jun 01 17:38:24 CEST 2016)
+Date: Mon, 20 Jun 2020 10:38:21 GMT
+Cache-Control: no-cache
+Access-Control-Allow-Origin: *
+Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes
+Access-Control-Allow-Headers: Content-Type, Authorization
+Pragma: no-cache
+X-OAuth-Scopes: change_password read_items read_fees read_patron
+X-Accepted-OAuth-Scopes: read_patron
+Content-Type: application/json; charset=UTF-8
+
+{
+  "doc": [
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:80086919",
+      "about": "Fernleihe mit Abholstation",
+      "condition": {
+        "http://purl.org/ontology/paia#PickupCondition": {
+          "option": [
+            {
+              "id": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR",
+              "about": "Zweigbibliothek Görlitz",
+              "amount": "2.50 EUR"
+            }
+        ]
+        }
+      },
+      "storage": "Hochschulbibliothek Zittau",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI"
+    },
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:02415857",
+      "about": "Die Eigenlogik der Städte",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:03039724",
+      "about": "Stadtsoziologie",
+      "storage": "Hochschulbibliothek Zittau",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI"
+    },
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:03201978",
+      "about": "Urbanität als Habitus",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 0,
+      "item": "ILL:13940",
+      "about": "Aushandlungen städtischer Größe - Eckert, Anna",
+      "label": "Medium im Vormerkregal",
+      "starttime": "2020-03-12T00:00:00+01:00"
+    }
+  ]
+}
diff --git a/module/finc/tests/fixtures/paia/response/fincWithUnknownOption.json b/module/finc/tests/fixtures/paia/response/fincWithUnknownOption.json
new file mode 100644
index 0000000000000000000000000000000000000000..e3120951b9cca8f9e4393574c317a8b3fd709b59
--- /dev/null
+++ b/module/finc/tests/fixtures/paia/response/fincWithUnknownOption.json
@@ -0,0 +1,65 @@
+HTTP/1.1 200 OK
+Server: vzg-paia/2.2-RC3 (Wed Jun 01 17:38:24 CEST 2016)
+Date: Mon, 20 Jun 2020 10:38:21 GMT
+Cache-Control: no-cache
+Access-Control-Allow-Origin: *
+Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes
+Access-Control-Allow-Headers: Content-Type, Authorization
+Pragma: no-cache
+X-OAuth-Scopes: change_password read_items read_fees read_patron
+X-Accepted-OAuth-Scopes: read_patron
+Content-Type: application/json; charset=UTF-8
+
+{
+  "doc": [
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:80086919",
+      "about": "Fernleihe mit Abholstation",
+      "condition": {
+        "http://purl.org/ontology/paia#PickupCondition": {
+          "option": [
+            {
+              "id": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/unknownLocation",
+              "about": "Zweigbibliothek Neu aber noch nicht bekannt / konfiguriert => soll nicht angezeigt werden"
+            }
+        ]
+        }
+      },
+      "storage": "Hochschulbibliothek Zittau",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI"
+    },
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:02415857",
+      "about": "Die Eigenlogik der Städte",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:03039724",
+      "about": "Stadtsoziologie",
+      "storage": "Hochschulbibliothek Zittau",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI"
+    },
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:03201978",
+      "about": "Urbanität als Habitus",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 0,
+      "item": "ILL:13940",
+      "about": "Aushandlungen städtischer Größe - Eckert, Anna",
+      "label": "Medium im Vormerkregal",
+      "starttime": "2020-03-12T00:00:00+01:00"
+    }
+  ]
+}
diff --git a/module/finc/tests/fixtures/paia/response/fincWithoutOptions.json b/module/finc/tests/fixtures/paia/response/fincWithoutOptions.json
new file mode 100644
index 0000000000000000000000000000000000000000..f75b1602ac84a2139575bf72f82023931b3292f7
--- /dev/null
+++ b/module/finc/tests/fixtures/paia/response/fincWithoutOptions.json
@@ -0,0 +1,55 @@
+HTTP/1.1 200 OK
+Server: vzg-paia/2.2-RC3 (Wed Jun 01 17:38:24 CEST 2016)
+Date: Mon, 20 Jun 2020 10:38:21 GMT
+Cache-Control: no-cache
+Access-Control-Allow-Origin: *
+Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes
+Access-Control-Allow-Headers: Content-Type, Authorization
+Pragma: no-cache
+X-OAuth-Scopes: change_password read_items read_fees read_patron
+X-Accepted-OAuth-Scopes: read_patron
+Content-Type: application/json; charset=UTF-8
+
+{
+  "doc": [
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:80086919",
+      "about": "Fernleihe mit Abholstation",
+      "storage": "Hochschulbibliothek Zittau",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI"
+    },
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:02415857",
+      "about": "Die Eigenlogik der Städte",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 4,
+      "item": "DE-Zi4:barcode:03039724",
+      "about": "Stadtsoziologie",
+      "storage": "Hochschulbibliothek Zittau",
+      "storageid": "http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI"
+    },
+    {
+      "status": 3,
+      "item": "DE-Zi4:barcode:03201978",
+      "about": "Urbanität als Habitus",
+      "renewals": 3,
+      "starttime": "2020-03-13T00:00:00+01:00",
+      "endtime": "2020-07-10T00:00:00+02:00",
+      "canrenew": true
+    },
+    {
+      "status": 0,
+      "item": "ILL:13940",
+      "about": "Aushandlungen städtischer Größe - Eckert, Anna",
+      "label": "Medium im Vormerkregal",
+      "starttime": "2020-03-12T00:00:00+01:00"
+    }
+  ]
+}
diff --git a/module/finc/tests/phpunit.xml b/module/finc/tests/phpunit.xml
index bd716aae1508c2e3ccd169baa8d13fc6b3c95068..6aacf6705b8fdc6170500a4fc4da35a23c2f89f2 100644
--- a/module/finc/tests/phpunit.xml
+++ b/module/finc/tests/phpunit.xml
@@ -6,4 +6,12 @@
         <testsuite name="fincTest">
             <directory>./unit-tests/src</directory>
         </testsuite>
+    <php>
+        <!-- Docker environment variables -->
+        <env name="LOCAL_CACHE_DIR" value="/usr/local/vufind/data/cache"/>
+        <env name="LOCAL_OVERRIDE_DIR" value="/usr/local/vufind/de_15/dev"/>
+        <env name="APPLICATION_PATH" value="/usr/local/vufind"/>
+        <env name="VUFIND_LOCAL_DIR" value="/usr/local/vufind/de_15/dev"/>
+        <env name="VUFIND_ENV" value="development"/>
+    </php>
 </phpunit>
diff --git a/module/finc/tests/unit-tests/src/fincTest/Config/SearchSpecsReaderTest.php b/module/finc/tests/unit-tests/src/fincTest/Config/SearchSpecsReaderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e172829e6cab132cffe3edd5c76ee73deb7b78d6
--- /dev/null
+++ b/module/finc/tests/unit-tests/src/fincTest/Config/SearchSpecsReaderTest.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Config SearchSpecsReader Test Class
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2010.
+ * Copyright (C) Leipzig University Library 2020.
+ *
+ * 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 Finc
+ * @package  Tests
+ * @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:testing:unit_tests Wiki
+ */
+namespace fincTest\Config;
+
+use Symfony\Component\Yaml\Yaml;
+use VuFind\Config\Locator;
+use VuFind\Config\SearchSpecsReader;
+
+/**
+ * Config SearchSpecsReader Test Class
+ *
+ * @category VuFind
+ * @package  Tests
+ * @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:testing:unit_tests Wiki
+ */
+class SearchSpecsReaderTest extends \VuFindTest\Config\SearchSpecsReaderTest
+{
+    /**
+     * Name of default yaml name to test.
+     *
+     * @var string
+     */
+    protected static $defaultYamlName = 'searchspecs.yaml';
+
+    /**
+     * YamlReader for SearchSpecs files.
+     *
+     * @var SearchSpecsReader
+     */
+    protected static $reader;
+
+    /**
+     * Path to symlinks and expected searchspecs result.
+     *
+     * @var string
+     */
+    protected $basePathToFixtures;
+
+    /**
+     * Path to local logging / printing.
+     *
+     * @var string
+     */
+    protected $basePathToLogging;
+
+    /**
+     * Override standard setup method.
+     *
+     * @return void
+     */
+    public static function setUpBeforeClass()
+    {
+        // Don't create test files, use existing.
+
+        // CAUTION, DONT DELETE REAL CONFIG FILES!
+        self::$filesToDelete = [];
+        self::$reader = new SearchSpecsReader();
+    }
+
+    /**
+     * Test @parent_yaml directive.
+     *
+     * @return void
+     */
+    public function testParentYaml()
+    {
+        if (self::$writeFailed) {
+            $this->markTestSkipped('Could not write test configurations.');
+        }
+        $this->basePathToFixtures = __DIR__ . "/../../../../fixtures/configs/yaml/searchspecs";
+        $this->basePathToLogging  = __DIR__ . "/log/";
+
+        /* check live finc searchspecs.yaml is equal to old /desired live searchspecs.yaml */
+        $this->isEcpectedSpec('local');
+
+        /* check alpha finc searchspecs.yaml is equal to old /desired alpha searchspecs.yaml */
+        $this->isEcpectedSpec('local/alpha');
+    }
+
+    /**
+     * @param string $environment
+     * @param bool $printResult
+     * @throws \ReflectionException
+     */
+    protected function isEcpectedSpec(string $environment, bool $printResult = false): void
+    {
+        $basePathAppendix   = "config/vufind/" . self::$defaultYamlName;
+
+        $vufindYaml     = "$this->basePathToFixtures/$basePathAppendix";
+        $currentEnvYaml = "$this->basePathToFixtures/$environment/$basePathAppendix";
+        $expectedYaml   = "$this->basePathToFixtures/result/$environment/" . self::$defaultYamlName;
+
+        $resultArray    = $this->callMethod(self::$reader, 'getFromPaths', [$vufindYaml, $currentEnvYaml]);
+        $expectedArray  = $this->callMethod(self::$reader, 'getFromPaths', [$expectedYaml]);
+
+        if ($printResult) {
+            file_put_contents("$this->basePathToLogging/input_reverse.yaml", Yaml::dump($resultArray, 4, 2));
+        }
+
+        $this->assertEquals(
+            $resultArray,
+            $expectedArray
+        );
+    }
+}
diff --git a/module/finc/tests/unit-tests/src/fincTest/ILS/Driver/PAIATest.php b/module/finc/tests/unit-tests/src/fincTest/ILS/Driver/PAIATest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8169a7e9ad06ef74b46563d1a974d2ae9086a4b4
--- /dev/null
+++ b/module/finc/tests/unit-tests/src/fincTest/ILS/Driver/PAIATest.php
@@ -0,0 +1,767 @@
+<?php
+/**
+ * ILS driver test
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Leipzig University Library 2020.
+ *
+ * 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  Tests
+ * @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 Main Page
+ */
+namespace VuFindTest\ILS\Driver;
+
+use finc\ILS\Driver\PAIA as fincPAIA;
+use InvalidArgumentException;
+use Zend\Http\Client\Adapter\Test as TestAdapter;
+use Zend\Http\Response as HttpResponse;
+
+/**
+ * ILS driver test
+ *
+ * @category VuFind
+ * @package  FincTest
+ * @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 Main Page
+ */
+class PAIAFincTest extends \VuFindTest\ILS\Driver\PAIATest
+{
+    public static $pickupCondition = 'http://purl.org/ontology/paia#PickupCondition';
+
+    public static $validConfigWithPickUpOption = [
+        'DAIA' =>
+            [
+                'baseUrl'           => 'http://daia.gbv.de/',
+            ],
+        'PAIA' =>
+            [
+                'baseUrl'           => 'http://paia.gbv.de/',
+                'paiaConditions'    =>
+                    [
+                        'http://purl.org/ontology/paia#PickupCondition'
+                    ]
+            ]
+    ];
+
+    public static $validConfigWithOptionForOtherInstance = [
+        'DAIA' =>
+            [
+                'baseUrl'           => 'http://daia.gbv.de/',
+            ],
+        'PAIA' =>
+            [
+                'baseUrl'           => 'http://paia.gbv.de/',
+                'paiaConditions'    =>
+                    [
+                        'http://purl.org/ontology/paia#PickupCondition' =>  'http://data.ub.uni-leipzig.de/resource/DE-15/department/wrongPickup'
+                    ]
+            ]
+    ];
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testMapOptionsPickupBranchTrue()
+    {
+        list($method, $paia) = $this->prepareByReflection();
+
+        $pickUpId       = 'http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR';
+        $pickUpAbout    = 'Zweigbibliothek Görlitz';
+        $input  = [
+            'condition' => [
+                self::$pickupCondition => [
+                    'option' => [
+                        0 => [
+                            'id'    => $pickUpId,
+                            'about' => $pickUpAbout
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        $conditions = [
+            self::$pickupCondition
+        ];
+
+        $output = [];
+        $method->invokeArgs($paia, [$input, &$output, $conditions]);
+
+        $this->assertEquals($pickUpId, $output['options'][self::$pickupCondition][0]['id'],
+            "Wrong pickup branch id!"
+        );
+
+        $this->assertEquals($pickUpAbout, $output['options'][self::$pickupCondition][0]['about'],
+            "Wrong pickup branch label!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testMapOptionsNewTrue()
+    {
+        list($method, $paia) = $this->prepareByReflection();
+
+        $pickUpId       = 'http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR';
+        $pickUpAbout    = 'Zweigbibliothek Görlitz';
+        $newOptionId    = 'http://data.ub.uni-leipzig.de/resource/DE-Zi4/newOption';
+        $newOption      = 'a new option within new condition';
+        $input  = [
+            'condition' => [
+                self::$pickupCondition => [
+                    'option' => [
+                        0 => [
+                            'id'    => $pickUpId,
+                            'about' => $pickUpAbout
+                        ]
+                    ]
+                ],
+                'http://purl.org/ontology/paia#newCondition' => [
+                    'option' => [
+                        0 => [
+                            'id' => $newOptionId,
+                            'about' => $newOption
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        $conditions = [
+            self::$pickupCondition,
+            'http://purl.org/ontology/paia#newCondition'
+        ];
+
+        $output = [];
+        $method->invokeArgs($paia, [$input, &$output, $conditions]);
+        $this->assertEquals($pickUpId, $output['options'][self::$pickupCondition][0]['id'],
+            "Wrong pickup branch id!"
+        );
+        $this->assertEquals($pickUpAbout, $output['options'][self::$pickupCondition][0]['about'],
+            "Wrong pickup branch label!"
+        );
+        $this->assertEquals($newOptionId, $output['options']['http://purl.org/ontology/paia#newCondition'][0]['id'],
+            "New option id missing!"
+        );
+        $this->assertEquals($newOption, $output['options']['http://purl.org/ontology/paia#newCondition'][0]['about'],
+            "New option label missing!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testMapOptionsPickupBranchFalseNoConditions()
+    {
+        list($method, $paia) = $this->prepareByReflection();
+
+        $pickUpBranch = 'Zweigbibliothek Görlitz';
+        $input  = [
+            'condition' => [
+                self::$pickupCondition => [
+                    'option' => [
+                        0 => [
+                            'id' => 'http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR',
+                            'about' => $pickUpBranch
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        $output = [];
+        $method->invokeArgs($paia, [$input, &$output, []]);
+        $this->assertFalse(isset($output["options"]),
+            "Unknown option was mapped"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testMapOptionsPickupBranchTrueUnknownOptionId()
+    {
+        /* validate new options otherwise, e.g. by checking $presentationPickups later */
+        list($method, $paia) = $this->prepareByReflection();
+
+        $pickUpBranch = 'Zweigbibliothek Görlitz';
+        $input  = [
+            'condition' => [
+                self::$pickupCondition => [
+                    'option' => [
+                        0 => [
+                            'id' => 'http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/fake',
+                            'about' => $pickUpBranch
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        $conditions = [
+            self::$pickupCondition
+        ];
+
+        $output = [];
+        $method->invokeArgs($paia, [$input, &$output, $conditions]);
+        $this->assertTrue(isset($output["options"]),
+            "Unknown option was not mapped"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testGetMyHoldsWithoutOptionConfigurationDefined()
+    {
+        $conn = $this->createConnector('fincWithOnePickupBranchOption.json');
+        /* default config */
+        $conn->setConfig($this->validConfig);
+        $conn->init();
+        $result = $conn->getMyHolds($this->patron);
+
+        $filtered = array_filter(
+            $result,
+            function ($resultItem) {
+                if (isset($resultItem['options'])) {
+                    return true;
+                }
+            }
+        );
+
+        /* no option or pickup branch */
+        $this->assertTrue(count($filtered) == 0,
+            "Two many items with options!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testGetMyHoldsWithoutPaiaResponseOptions()
+    {
+        $conn = $this->createConnector('fincWithoutOptions.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $result = $conn->getMyHolds($this->patron);
+
+        $filtered = array_filter(
+            $result,
+            function ($resultItem) {
+                if (isset($resultItem['options'])) {
+                    return true;
+                }
+            }
+        );
+
+        /* no option nor pickup branch */
+        $this->assertTrue(count($filtered) == 0,
+            "Two many items with options!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testGetMyHoldsAllowUnknownOptions()
+    {
+        $conn = $this->createConnector('fincWithUnknownOption.json');
+        /* default config */
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $result = $conn->getMyHolds($this->patron);
+
+        $filtered = array_filter(
+            $result,
+            function ($resultItem) {
+                if (isset($resultItem['options'])) {
+                    return true;
+                }
+            }
+        );
+
+        /* no option */
+        $this->assertTrue(count($filtered) > 0,
+            "Two many items with options!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testGetMyHoldsWrongInstance()
+    {
+        $conn = $this->createConnector('fincWithOnePickupBranchOption.json');
+        /* default config */
+        $conn->setConfig(self::$validConfigWithOptionForOtherInstance);
+        $conn->init();
+        $result = $conn->getMyHolds($this->patron);
+
+        $filtered = array_filter(
+            $result,
+            function ($resultItem) {
+                if (isset($resultItem['options'])) {
+                    return true;
+                }
+            }
+        );
+
+        /* no option */
+        $this->assertTrue(count($filtered) == 0,
+            "Two many items with options!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testGetMyHoldsWithPickUpBranch()
+    {
+        $conn = $this->createConnector('fincWithOnePickupBranchOption.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $result = $conn->getMyHolds($this->patron);
+
+        $filtered = array_filter(
+            $result,
+            function ($resultItem) {
+                if (isset($resultItem['options'])) {
+                    return true;
+                }
+            }
+        );
+
+        /* check pickupbranch */
+        $this->assertTrue(count($filtered[0]["options"][self::$pickupCondition]) > 0,
+            "No pickup branch mapped!");
+
+        $this->assertEquals("Zweigbibliothek Görlitz", $filtered[0]["options"][self::$pickupCondition][0]["about"],
+            "Wrong pickup branch!"
+        );
+
+        /* but keep storage id (location) */
+        $this->assertEquals("Hochschulbibliothek Zittau", $filtered[0]["location"],
+            "Wrong pickup branch!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testGetMyHoldsWithAnotherPickUpBranch()
+    {
+        $conn = $this->createConnector('fincWithAnotherPickupBranchOption.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $result = $conn->getMyHolds($this->patron);
+
+        $filtered = array_filter(
+            $result,
+            function ($resultItem) {
+                if (isset($resultItem['options'])) {
+                    return true;
+                }
+            }
+        );
+
+        /* check pickupbranch */
+        $this->assertTrue(count($filtered[0]["options"][self::$pickupCondition]) > 0,
+            "No pickup branch mapped!");
+
+        $this->assertEquals("http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI", $filtered[0]["options"][self::$pickupCondition][0]["id"],
+            "Wrong pickup branch id!"
+        );
+
+        $this->assertEquals("Zweigbibliothek Zittau", $filtered[0]["options"][self::$pickupCondition][0]["about"],
+            "Wrong pickup branch label!"
+        );
+
+        $this->assertEquals("2,50 EUR", $filtered[0]["options"][self::$pickupCondition][0]["amount"],
+            "Wrong pickup branch amount!"
+        );
+
+        /* but keep storage id (location) */
+        $this->assertEquals("Hochschulbibliothek Görlitz", $filtered[0]["location"],
+            "Wrong pickup branch!"
+        );
+    }
+
+    /**
+     * Test Multiple Options => use first option when displaying items
+     *
+     * @return void
+     */
+    public function testGetMyHoldsWithMultipleOptionsPickUpBranch()
+    {
+        $conn = $this->createConnector('fincWithMultiplePickupBranchOptions.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $result = $conn->getMyHolds($this->patron);
+
+        $filtered = array_filter(
+            $result,
+            function ($resultItem) {
+                if (isset($resultItem['options'])) {
+                    return true;
+                }
+            }
+        );
+
+        /* check pickupbranch */
+        $this->assertTrue(count($filtered) == 1,
+            "No pickup branch mapped!");
+
+        $this->assertEquals("http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwZI", $filtered[0]["options"][self::$pickupCondition][0]["id"],
+            "Wrong pickup branch id!"
+        );
+
+        $this->assertEquals("Zweigbibliothek Zittau", $filtered[0]["options"][self::$pickupCondition][0]["about"],
+            "Wrong pickup branch label!"
+        );
+
+        $this->assertEquals("http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR", $filtered[0]["options"][self::$pickupCondition][1]["id"],
+            "Wrong pickup branch id!"
+        );
+
+        $this->assertEquals("Zweigbibliothek Görlitz", $filtered[0]["options"][self::$pickupCondition][1]["about"],
+            "Wrong pickup branch label!"
+        );
+
+        /* but keep storage id (location) */
+        $this->assertEquals("Hochschulbibliothek Görlitz", $filtered[0]["location"],
+            "Wrong pickup branch!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testgetOptionsReturnsIDTrue()
+    {
+        $conn = $this->createConnector('fincWithMultiplePickupBranchOptions.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $options['options'][self::$pickupCondition][0]['id'] = 'TESTID';
+
+        $this->assertEquals(
+            ['TESTID'],
+            $conn->getOptions(self::$pickupCondition, $options, true),
+            "Wrong option value"
+        );
+
+        $validOptions[0][0] = 'TESTID';
+        $this->assertEquals(
+            ['TESTID'],
+            $conn->getOptions(self::$pickupCondition, $options, true, $validOptions),
+            "Wrong option value"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testgetOptionsNotOnlyFirstButAll()
+    {
+        $conn = $this->createConnector('fincWithMultiplePickupBranchOptions.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $options['options'][self::$pickupCondition][0]['id'] = 'TESTID1';
+        $options['options'][self::$pickupCondition][1]['id'] = 'TESTID2';
+
+        $this->assertEquals(
+            ['TESTID1', 'TESTID2'],
+            $conn->getOptions(self::$pickupCondition, $options, false),
+            "Wrong option value"
+        );
+
+        $validOptions[0][0] = 'TESTID1';
+        $validOptions[0][1] = 'TESTID2';
+        $this->assertEquals(
+            ['TESTID1', 'TESTID2'],
+            $conn->getOptions(self::$pickupCondition, $options, false, $validOptions),
+            "Wrong option value"
+        );
+
+        /* first option only label set */
+        $options['options'][self::$pickupCondition][0]['id']    = null;
+        $options['options'][self::$pickupCondition][0]['about'] = 'TESTLABEL1';
+        $options['options'][self::$pickupCondition][1]['id']    = 'TESTID2';
+
+        $this->assertEquals(
+            ['TESTLABEL1', 'TESTID2'],
+            $conn->getOptions(self::$pickupCondition, $options, false, null),
+            "Wrong option value"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testgetOptionsReturnsLabelTrue()
+    {
+        $conn = $this->createConnector('fincWithMultiplePickupBranchOptions.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+        $options['options'][self::$pickupCondition][0]['id'] = null;
+        $options['options'][self::$pickupCondition][0]['amount'] = '2,50 Euro';
+        $options['options'][self::$pickupCondition][0]['about'] = 'use label when id missing';
+
+        $this->assertEquals(
+            ['use label when id missing'],
+            $conn->getOptions(self::$pickupCondition, $options, true),
+            "Wrong option value!"
+        );
+
+        /* also return label instead of id when no valid options are given */
+        $options['options'][self::$pickupCondition][0]['id'] = 'http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR';
+        $options['options'][self::$pickupCondition][0]['about'] = 'Take label, if parameter valid Options missing';
+        $this->assertEquals(
+            ['Take label, if parameter valid Options missing'],
+            $conn->getOptions(self::$pickupCondition, $options, true),
+            "Wrong option value!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testgetOptionsReturnsEmptyArray()
+    {
+        $conn = $this->createConnector('fincWithMultiplePickupBranchOptions.json');
+        $conn->setConfig(self::$validConfigWithPickUpOption);
+        $conn->init();
+
+        $options['options'][self::$pickupCondition][0]['id'] = 'http://data.ub.uni-leipzig.de/resource/DE-Zi4/department/zwGR';
+        $options['options'][self::$pickupCondition][0]['about'] = 'Zweigbibliothek Görlitz';
+
+        /* invalid condition */
+        $this->assertEquals(
+            [],
+            $conn->getOptions('fakeCondition', $options, true),
+            "Wrong option value!"
+        );
+
+        /* no options in result array */
+        $this->assertEquals(
+            [],
+            $conn->getOptions('fakeCondition', [], true),
+            "Wrong option value!"
+        );
+
+        /* invalid option */
+        $validOptions[0][0] = 'xxx';
+        $this->assertEquals(
+            [],
+            $conn->getOptions(self::$pickupCondition, $options, true, $validOptions),
+            "Wrong option value!"
+        );
+
+        /* no option values */
+        $options['options'][self::$pickupCondition][0]['id'] = null;
+        $options['options'][self::$pickupCondition][0]['about'] = null;
+        $this->assertEquals(
+            [""],
+            $conn->getOptions(self::$pickupCondition, $options, true),
+            "Wrong option value!"
+        );
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testCallMapOptionsTrue() : void
+    {
+        $paia = $this->getMockBuilder(PAIAMock::class)
+            ->enableProxyingToOriginalMethods()
+            ->setMethods(['mapOptions'])
+            ->setConstructorArgs([new \VuFind\Date\Converter(), new \Zend\Session\SessionManager()])
+            ->getMock();
+
+        $paia->prepare("fincWithOnePickupBranchOption.json");
+
+        $paia->expects($this->atLeastOnce())
+            ->method('mapOptions');
+
+        /* only used by holdings so far */
+        $paia->getMyHolds($this->patron);
+    }
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testCallMapOptionsFalse() : void
+    {
+        $paia = $this->getMockBuilder(PAIAMock::class)
+            ->enableProxyingToOriginalMethods()
+            ->setMethods(['mapOptions'])
+            ->setConstructorArgs([new \VuFind\Date\Converter(), new \Zend\Session\SessionManager()])
+            ->getMock();
+
+        $paia->prepare("fincWithOnePickupBranchOption.json");
+
+        $paia->expects($this->never())
+            ->method('mapOptions');
+
+        $paia->getMyILLRequests($this->patron);
+        $paia->getMyFines($this->patron);
+        $paia->getMyStorageRetrievalRequests($this->patron);
+        $paia->getMyTransactions($this->patron);
+    }
+
+    /**
+     * Create connector with fixture file.
+     *
+     * @param string $fixture Fixture file
+     *
+     * @return Connector
+     *
+     * @throws InvalidArgumentException Fixture file does not exist
+     */
+    protected function createConnector($fixture = null)
+    {
+        if (empty($fixture) || strpos($fixture, 'finc') === false) {
+            return parent::createConnector($fixture);
+        }
+
+        $adapter = new TestAdapter();
+        if ($fixture) {
+            $file = realpath(
+                __DIR__ .
+                '/../../../../../fixtures/paia/response/' . $fixture
+            );
+            if (!is_string($file) || !file_exists($file) || !is_readable($file)) {
+                throw new InvalidArgumentException(
+                    sprintf('Unable to load fixture file: %s ', $file)
+                );
+            }
+            $response = file_get_contents($file);
+            $responseObj = HttpResponse::fromString($response);
+            $adapter->setResponse($responseObj);
+        }
+        $service = new \VuFindHttp\HttpService();
+        $service->setDefaultAdapter($adapter);
+        $conn = new PAIAMock(
+            new \VuFind\Date\Converter(),
+            new \Zend\Session\SessionManager()
+        );
+        $conn->setHttpService($service);
+        return $conn;
+    }
+
+    /**
+     * @return array
+     * @throws \ReflectionException
+     */
+    protected function prepareByReflection()
+    {
+        /* use reflection due to protected access level */
+        $class = new \ReflectionClass('finc\ILS\Driver\PAIA');
+        $method = $class->getMethod("mapOptions");
+        $method->setAccessible(true);
+
+        $paia = new fincPAIA(
+            new \VuFind\Date\Converter(),
+            new \Zend\Session\SessionManager()
+        );
+
+        return [$method, $paia];
+    }
+}
+
+/* helper class to make protected finc methods public and reuse Vufind mocking approach */
+class PAIAMock extends fincPAIA
+{
+    public function prepare($fixture)
+    {
+        $adapter = new TestAdapter();
+        if ($fixture) {
+            $file = realpath(
+                __DIR__ .
+                '/../../../../../fixtures/paia/response/' . $fixture
+            );
+            if (!is_string($file) || !file_exists($file) || !is_readable($file)) {
+                throw new InvalidArgumentException(
+                    sprintf('Unable to load fixture file: %s ', $file)
+                );
+            }
+            $response = file_get_contents($file);
+            $responseObj = HttpResponse::fromString($response);
+            $adapter->setResponse($responseObj);
+        }
+        $service = new \VuFindHttp\HttpService();
+        $service->setDefaultAdapter($adapter);
+        $this->setHttpService($service);
+        $this->setConfig(PAIAFincTest::$validConfigWithPickUpOption);
+        parent::init();
+    }
+
+    public function parsePAIAJson($file)
+    {
+        return $this->paiaParseJsonAsArray($file);
+    }
+
+    public function myHoldsMapping($items)
+    {
+        return parent::myHoldsMapping($items);
+    }
+
+    public function paiaGetItems($patron, $filter = [])
+    {
+        return parent::paiaGetItems($patron, $filter);
+    }
+
+    public function paiaCheckScope($scope)
+    {
+        return true;
+    }
+}
diff --git a/themes/finc/scss/compiled.scss b/themes/finc/scss/compiled.scss
index 5de35874c1530a7d73e880eecd1ac4017d48d688..c7c3e947032f2993164611e444831ae815a12345 100644
--- a/themes/finc/scss/compiled.scss
+++ b/themes/finc/scss/compiled.scss
@@ -2360,6 +2360,11 @@ footer ul {
 // FOOTER - END
 
 // MODALS
+//// Prevent transparent lightbox bug -- keep !important to override!
+.modal-dialog .mainbody.left {
+  float: none !important;
+}
+
 //// Set modal width to make better use of the screen
 @media (max-width: $screen-xs-max) {
   .modal-dialog {
diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/data-additionals.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/data-additionals.phtml
index fbb7def9e17e2281bbec2900454076631813c747..7ee1dc4a8e260244adb97a318820e7075cf26164 100644
--- a/themes/finc/templates/RecordDriver/DefaultRecord/data-additionals.phtml
+++ b/themes/finc/templates/RecordDriver/DefaultRecord/data-additionals.phtml
@@ -2,23 +2,16 @@
 <?php if (!empty($data) && is_array($data)): ?>
   <?php foreach ($data as $additional) : ?>
     <?php if (isset($additional['identifier'])): ?>
-      <tr>
-        <th>
-          <?=$this->transEsc($additional['identifier'])?>:
-        </th>
-        <td>
-            <?php if(isset($additional['id']) && $url = $this->recordLink()->getRecordLink($additional['id'],'id')): ?>
-                <?php if (isset($additional['label']) && !empty($additional['label'])): ?>
-                    <a href="<?=$url?>"><?=$this->escapeHtml($additional['label'])?></a><?=$this->escapeHtml($additional['suffix'])?>
-                <?php else: ?>
-                    <a href="<?=$url?>"><?=$this->escapeHtml($additional['text'])?></a>
-                <?php endif; ?>
-                <?php unset($url) ?>
+        <?php if(isset($additional['id']) && $url = $this->recordLink()->getRecordLink($additional['id'],'id')): ?>
+            <?php if (isset($additional['label']) && !empty($additional['label'])): ?>
+                <a href="<?=$url?>"><?=$this->escapeHtml($additional['label'])?></a><?=$this->escapeHtml($additional['suffix'])?>
             <?php else: ?>
-                <?=$this->escapeHtml($additional['text'])?>
+                <a href="<?=$url?>"><?=$this->escapeHtml($additional['text'])?></a>
             <?php endif; ?>
-        </td>
-      </tr>
+            <?php unset($url) ?>
+        <?php else: ?>
+            <?=$this->escapeHtml($additional['text'])?>
+        <?php endif; ?>
     <?php endif; ?>
   <?php endforeach; ?>
 <?php endif; ?>
diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/data-otherRelationshipEntry.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/data-otherRelationshipEntry.phtml
index e57ce111e50868f7d50cc4ee96f7b2cb2054e976..7ec5de28450e5453f4c1bada47f171d41c67266f 100644
--- a/themes/finc/templates/RecordDriver/DefaultRecord/data-otherRelationshipEntry.phtml
+++ b/themes/finc/templates/RecordDriver/DefaultRecord/data-otherRelationshipEntry.phtml
@@ -1,19 +1,14 @@
 <!-- finc: RecordDriver - DefaultRecord - data-otherRelationshipEntry -->
 <?php if (!empty($data)): ?>
   <?php foreach ($data as $subject => $values): ?>
-    <tr>
-      <th><?=$this->transEsc($values[0]['subject'])?>: </th>
-      <td>
-        <?php foreach ($values as $v): ?>
-          <?php if (isset($v['id'])): ?>
-            <a href="<?=$this->recordLink()->getUrl($v['id'])?>"><?=$this->escapeHtml($v['text'])?></a>
-          <?php else: ?>
-            <?=$this->escapeHtml($v['text'])?>
-          <?php endif; ?>
-          <br/>
-        <?php endforeach; ?>
-      </td>
-    </tr>
+    <?php foreach ($values as $v): ?>
+      <?php if (isset($v['id'])): ?>
+        <a href="<?=$this->recordLink()->getUrl($v['id'])?>"><?=$this->escapeHtml($v['text'])?></a>
+      <?php else: ?>
+        <?=$this->escapeHtml($v['text'])?>
+      <?php endif; ?>
+      <br/>
+    <?php endforeach; ?>
   <?php endforeach; ?>
 <?php endif; ?>
 <!-- finc: RecordDriver - DefaultRecord - data-otherRelationshipEntry - END -->
diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/data-titleUniform.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/data-titleUniform.phtml
index 237b057941bb913d62df0b1a3df44b3db681e6a5..3ff56c5bf39a67b811014ef6cb7b15a3f0528ba3 100644
--- a/themes/finc/templates/RecordDriver/DefaultRecord/data-titleUniform.phtml
+++ b/themes/finc/templates/RecordDriver/DefaultRecord/data-titleUniform.phtml
@@ -1,25 +1,16 @@
 <!-- finc: RecordDriver - DefaultRecord - data-titleUniform -->
 <?php if (!empty($data)): ?>
-  <tr>
-    <th>
-      <?=$this->driver->isRDA()
-        ? $this->transEsc('rda_original_title')
-        : $this->transEsc('non_rda_original_title')?>:
-    </th>
-    <td property="title">
-      <?php if (is_array($data)): ?>
-        <?php if ($data['titleUniform']): ?>
-          <a href="<?= $this->record($this->driver)->getLink('titleUniform', $data['titleUniform']) ?>">
-        <?php endif; ?>
-        <?= $this->escapeHtml($data['title']) ?>
-        <?php if ($data['titleUniform']): ?>
-          </a>
-        <?php endif; ?>
-        <?php if (isset($data['lang'])): ?> &#x27E8;<?= $this->escapeHtml($data['lang']) ?>&#x27E9;<?php endif; ?>
-      <?php else: ?>
-        <a href="<?=$this->record($this->driver)->getLink('title', $data)?>"><?=$this->escapeHtml($data)?></a>
-      <?php endif; ?>
-    </td>
-  </tr>
+  <?php if (is_array($data)): ?>
+    <?php if (isset($data['titleUniform'])): ?>
+      <a href="<?= $this->record($this->driver)->getLink('titleUniform', $data['titleUniform']) ?>">
+    <?php endif; ?>
+    <?= $this->escapeHtml($data['title']) ?>
+    <?php if (isset($data['titleUniform'])): ?>
+      </a>
+    <?php endif; ?>
+    <?php if (isset($data['lang'])): ?> &#x27E8;<?= $this->escapeHtml($data['lang']) ?>&#x27E9;<?php endif; ?>
+  <?php else: ?>
+    <a href="<?=$this->record($this->driver)->getLink('title', $data)?>"><?=$this->escapeHtml($data)?></a>
+  <?php endif; ?>
 <?php endif; ?>
 <!-- finc: RecordDriver - DefaultRecord - data-titleUniform - END -->
diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml
index c3381f131baaa6724b1c7ca2ea84bad9af34e510..d0d8326caaef4bbc5d495c3e14216d16edcce82e 100644
--- a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml
+++ b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml
@@ -4,11 +4,11 @@
 $id = $this->driver->getUniqueId();
 $source = $this->driver->getSourceIdentifier();
 if (isset($this->list) && is_object($this->list)) {
-  $list_id = $this->list->id;
-  $user_id = $this->list->user_id;
+    $list_id = $this->list->id;
+    $user_id = $this->list->user_id;
 } else {
-  $list_id = null;
-  $user_id = $this->user ? $this->user->id : null;
+    $list_id = null;
+    $user_id = $this->user ? $this->user->id : null;
 }
 // finc: next line finc-specific; required to display public favorites lists, #12052, see also below - CK
 $isEditable = $this->user && $this->user->id === $user_id;
@@ -91,15 +91,15 @@ if ($cover):
 
         <div class="last">
           <?php if (!$this->driver->isCollection()) {
-            if ($snippet = $this->driver->getHighlightedSnippet()) {
-              if (!empty($snippet['caption'])) {
-                echo '<strong>' . $this->transEsc($snippet['caption']) . ':</strong> ';
-              }
-              if (!empty($snippet['snippet'])) {
-                echo '<span class="quotestart">&#8220;</span>...' . $this->highlight($snippet['snippet']) . '...<span class="quoteend">&#8221;</span><br/>';
-              }
-            }
-          } ?>
+                if ($snippet = $this->driver->getHighlightedSnippet()) {
+                    if (!empty($snippet['caption'])) {
+                        echo '<strong>' . $this->transEsc($snippet['caption']) . ':</strong> ';
+                    }
+                    if (!empty($snippet['snippet'])) {
+                        echo '<span class="quotestart">&#8220;</span>...' . $this->highlight($snippet['snippet']) . '...<span class="quoteend">&#8221;</span><br/>';
+                    }
+                }
+            } ?>
 
           <?php $listTags = ($this->usertags()->getMode() !== 'disabled') ? $this->driver->getTags(
             null === $list_id ? true : $list_id, // get tags for all lists if no single list is selected
@@ -126,7 +126,7 @@ if ($cover):
             <strong><?=$this->transEsc('Saved in')?>:</strong>
             <?php $i = 0;
             foreach ($this->lists as $current): ?>
-                  <a href="<?=$this->url('userList', ['id' => $current->id])?>"><?=$this->escapeHtml($current->title)?></a><?php if($i++ < count($this->lists) - 1): ?>,<?php endif; ?>
+                  <a href="<?=$this->url('userList', ['id' => $current->id])?>"><?=$this->escapeHtml($current->title)?></a><?php if ($i++ < count($this->lists) - 1): ?>,<?php endif; ?>
             <?php endforeach; ?>
             <br/>
           <?php endif; ?>
@@ -174,8 +174,8 @@ if ($cover):
           <?php endif; ?>
 
             <?php if (!is_array($urls)) {
-            $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"
@@ -208,16 +208,45 @@ if ($cover):
             : $this->url('userList', ['id' => $list_id]);
           $deleteUrlGet = $deleteUrl . '?delete=' . urlencode($id) . '&amp;source=' . urlencode($source);
 
+          /* #17712 not necessary to fetch items after deleting by ajax in controller */
+          $deleteUrl .= '?layout=lightbox';
           $dLabel = 'delete-label-' . preg_replace('[\W]', '-', $id);
           ?>
           <div class="dropdown">
-            <i class="fa fa-fw fa-trash-o" aria-hidden="true"></i> <a class="dropdown-toggle" id="<?=$dLabel?>" role="button" data-toggle="dropdown" href="<?=$deleteUrlGet?>">
-              <?=$this->transEsc('Delete')?>
+            <i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
+            <a class="dropdown-toggle" id="<?= $dLabel ?>"
+               role="button" data-toggle="dropdown"
+               href="<?= $deleteUrlGet ?>">
+              <?= $this->transEsc('Delete') ?>
             </a>
-            <ul class="dropdown-menu" role="menu" aria-labelledby="<?=$dLabel?>">
+            <ul class="dropdown-menu" role="menu" aria-labelledby="<?= $dLabel ?>">
               <li>
-                <a href="javascript:" onClick="$.post('<?=$deleteUrl?>', {'delete':'<?=$this->escapeJs($id)?>','source':'<?=$this->escapeJs($source)?>','confirm':true},function(){location.reload(true)})" title="<?=$this->transEsc('confirm_delete_brief')?>"><?=$this->transEsc('confirm_dialog_yes')?></a></li>
-              <li><a href="javascript:document.getElementById('<?=$dLabel?>').focus();"><?=$this->transEsc('confirm_dialog_no')?></a></li>
+                <?php /* #17711 give user feedback and dont reload page after deleting */ ?>
+                <a title="<?= $this->transEsc('confirm_delete_brief') ?>" onClick="$.post(
+                  '<?= $deleteUrl ?>',
+                  {
+                    'delete':'<?= $this->escapeJs($id) ?>',
+                    'source':'<?= $this->escapeJs($source) ?>',
+                    'confirm':true
+                  }, function(){
+                      $('input[value=<?=$id?>]').parent().remove(0);
+                      <?php /* reset needed for possible old MyResearchController::mylistAction in instances withot clearMessages */ ?>
+                      <?php if (null === $list_id) :?>
+                        VuFind.lightbox.alert('<?= $this->transEsc('Item removed from favorites')?>', 'success');
+                      <?php else :?>
+                        VuFind.lightbox.alert('<?= $this->transEsc('Item removed from list')?>', 'success');
+                      <?php endif; ?>
+                  }).fail(function(data) {
+                      $('.fa-spinner.fa-spin').removeClass('fa-spinner fa-spin').addClass('fa-trash-o');
+                      VuFind.lightbox.alert(
+                        '<?= $this->transEsc('Delete') . ' ' . $this->transEsc('of') . ' ' . htmlspecialchars($this->record($this->driver)->getTitleHtml()) . ': ' . $this->transEsc('errorcode_error')?>',
+                        'danger'
+                      )
+                  });
+                  $(this).closest('.dropdown').find('.fa-trash-o').removeClass('fa-trash-o').addClass('fa-spinner fa-spin');">
+                  <?= $this->transEsc('confirm_dialog_yes') ?>
+                </a>
+              </li>
             </ul>
           </div>
 
diff --git a/themes/finc/templates/RecordDriver/SolrLido/data-events.phtml b/themes/finc/templates/RecordDriver/SolrLido/data-events.phtml
index 8030e6f371c7e39bc1798efa91466c96d07d19bb..504ed249498f2ff21ae3a604253a8ccc1b6936ab 100644
--- a/themes/finc/templates/RecordDriver/SolrLido/data-events.phtml
+++ b/themes/finc/templates/RecordDriver/SolrLido/data-events.phtml
@@ -1,18 +1,5 @@
-<?php $publicationIsSet = false; ?>
-<?php if (count($event = $this->driver->getEvents()) > 0): ?>
-  <?php foreach ($event as $eventType => $events): ?>
-    <tr class="recordEvents">
-      <th>
-        <?php if ($eventType == 'production'): ?>
-          <?=$this->transEsc('expression creation')?>:
-        <?php elseif ($eventType == 'publication'): ?>
-          <?=$this->transEsc('Time of origin')?>:
-          <?php $publicationIsSet = true; ?>
-        <?php else: ?>
-          <?=$this->transEsc($eventType)?>:
-        <?php endif; ?>
-      </th>
-      <td>
+ <?php foreach ($data as $eventType => $events): ?>
+     <?php $publicationIsSet = $eventType === 'Publication'; ?>
         <div class="truncate-field">
           <?php foreach ($events as $event): ?>
             <?php if ($event != $events[0]): ?><br/><?php endif; ?>
@@ -36,7 +23,4 @@
             <?php if (!empty($event['description'])): ?><?=$this->escapeHtml($event['description'])?><br/><?php endif; ?>
           <?php endforeach; ?>
         </div>
-      </td>
-    </tr>
   <?php endforeach; ?>
-<?php endif; ?>
diff --git a/themes/finc/templates/RecordTab/holdingsils.phtml b/themes/finc/templates/RecordTab/holdingsils.phtml
index f2788c10708ecb7d90c825df1723e21e5fe952a5..0d631f88d4fdcc04b4d869b7d208cf1fe9e8bd83 100644
--- a/themes/finc/templates/RecordTab/holdingsils.phtml
+++ b/themes/finc/templates/RecordTab/holdingsils.phtml
@@ -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->escapeHtml($this->transEsc($current['desc']))?></a>
+      <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>"><?=$this->translate($current['desc'])?></a>
       <br/>
     <?php endforeach; ?>
   <?php endif; ?>