From 9d264f6f9dc38dfd64d5085c4cae03ea5c7a8f21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Lahmann?= <lahmann@ub.uni-leipzig.de>
Date: Fri, 13 Mar 2015 10:00:13 +0100
Subject: [PATCH] refs #4826: * implemented RecordDriver SolrAI.php *
 implemented ResolverDriver Redi.php * modified core, staff view for SolrAI *
 modified openurl.js * added .ini file for SolrAI * added OpenUrl settings for
 dev/config.ini.sample

---
 local/alpha/config/vufind/SolrAI.ini          |  22 +
 local/config/vufind/SolrAI.ini                |   3 +
 local/dev/config/vufind/SolrAI.ini.sample     |  26 +
 local/dev/config/vufind/config.ini.sample     |  13 +
 local/languages/de.ini                        |   5 +-
 local/languages/en.ini                        |   4 +
 .../VuFind/Search/Solr/MultiIndexListener.php |   6 +-
 .../VuFind/src/VuFind/Search/Solr/Params.php  |   2 +-
 module/finc/config/module.config.php          |  18 +-
 module/finc/src/finc/RecordDriver/Factory.php |  16 +
 module/finc/src/finc/RecordDriver/SolrAI.php  | 867 ++++++++++++++++++
 .../src/finc/RecordDriver/SolrDefault.php     |  59 +-
 .../finc/src/finc/Resolver/Driver/Factory.php |  58 ++
 module/finc/src/finc/Resolver/Driver/Redi.php | 318 +++++++
 .../src/finc/View/Helper/Root/Factory.php     |  56 ++
 .../finc/src/finc/View/Helper/Root/Record.php |  72 ++
 themes/finc/js/openurl.js                     |  41 +
 .../templates/RecordDriver/SolrAI/core.phtml  | 264 ++++++
 .../RecordDriver/SolrAI/link-isn.phtml        |   1 +
 .../RecordDriver/SolrAI/result-list.phtml     | 193 ++++
 .../templates/RecordTab/description.phtml     | 242 +++++
 .../templates/RecordTab/staffviewarray.phtml  |  23 +
 .../finc/templates/ajax/resolverLinks.phtml   |  51 ++
 themes/finc/theme.config.php                  |   7 +-
 24 files changed, 2323 insertions(+), 44 deletions(-)
 create mode 100644 local/alpha/config/vufind/SolrAI.ini
 create mode 100644 local/config/vufind/SolrAI.ini
 create mode 100644 local/dev/config/vufind/SolrAI.ini.sample
 create mode 100644 module/finc/src/finc/RecordDriver/SolrAI.php
 create mode 100644 module/finc/src/finc/Resolver/Driver/Factory.php
 create mode 100644 module/finc/src/finc/Resolver/Driver/Redi.php
 create mode 100644 module/finc/src/finc/View/Helper/Root/Factory.php
 create mode 100644 module/finc/src/finc/View/Helper/Root/Record.php
 create mode 100644 themes/finc/js/openurl.js
 create mode 100644 themes/finc/templates/RecordDriver/SolrAI/core.phtml
 create mode 100644 themes/finc/templates/RecordDriver/SolrAI/link-isn.phtml
 create mode 100644 themes/finc/templates/RecordDriver/SolrAI/result-list.phtml
 create mode 100644 themes/finc/templates/RecordTab/description.phtml
 create mode 100644 themes/finc/templates/RecordTab/staffviewarray.phtml
 create mode 100644 themes/finc/templates/ajax/resolverLinks.phtml

diff --git a/local/alpha/config/vufind/SolrAI.ini b/local/alpha/config/vufind/SolrAI.ini
new file mode 100644
index 00000000000..2860cc75855
--- /dev/null
+++ b/local/alpha/config/vufind/SolrAI.ini
@@ -0,0 +1,22 @@
+;####################################################################
+;##################### DO NOT DELETE THIS HEADER ####################
+;################### Leipzig University Library © 2015 ##############
+;
+; This is the default ALPHA-INI-file and inherits
+; all the settings from the INI-file defined in [Parent_Config] which
+; points to the default INI-file located in the folder vufind2/local
+;
+
+[Parent_Config]
+relative_path = ../../../config/vufind/SolrAI.ini
+
+; A comma-separated list of config sections from the parent which should be
+; completely overwritten by the equivalent sections in this configuration;
+; any sections not listed here will be merged on a section-by-section basis.
+;override_full_sections = "Languages,AlphaBrowse_Types"
+
+;
+;       Add ALPHA-specific customization after this header.
+;
+;##################### DO NOT DELETE THIS HEADER ####################
+;####################################################################
diff --git a/local/config/vufind/SolrAI.ini b/local/config/vufind/SolrAI.ini
new file mode 100644
index 00000000000..4ace37943b2
--- /dev/null
+++ b/local/config/vufind/SolrAI.ini
@@ -0,0 +1,3 @@
+[General]
+; Set the URI-pattern of the server which serves the raw Ai-record-data.
+baseUrl = "https://ai.ub.uni-leipzig.de/blob?%s"
diff --git a/local/dev/config/vufind/SolrAI.ini.sample b/local/dev/config/vufind/SolrAI.ini.sample
new file mode 100644
index 00000000000..84c18ffc83b
--- /dev/null
+++ b/local/dev/config/vufind/SolrAI.ini.sample
@@ -0,0 +1,26 @@
+;####################################################################
+;##################### DO NOT DELETE THIS HEADER ####################
+;################### Leipzig University Library © 2015 ##############
+;
+; This is the default DEV-INI-file and inherits
+; all the settings from the INI-file defined in [Parent_Config] which
+; points to the default INI-file located in the folder vufind2/local
+;
+
+;[Parent_Config]
+;relative_path = ../../../config/vufind/SolrAI.ini
+
+; A comma-separated list of config sections from the parent which should be
+; completely overwritten by the equivalent sections in this configuration;
+; any sections not listed here will be merged on a section-by-section basis.
+;override_full_sections = "Languages,AlphaBrowse_Types"
+
+;
+;       Add DEV-specific customization after this header.
+;
+;##################### DO NOT DELETE THIS HEADER ####################
+;####################################################################
+
+[General]
+; base url to ai blob server
+baseUrl = "https://ai.ub.uni-leipzig.de/blob?%s"
diff --git a/local/dev/config/vufind/config.ini.sample b/local/dev/config/vufind/config.ini.sample
index 1f3c3a72f60..8f5345b61d2 100644
--- a/local/dev/config/vufind/config.ini.sample
+++ b/local/dev/config/vufind/config.ini.sample
@@ -41,3 +41,16 @@ hash_passwords = "1"
 encrypt_ils_password = "1"
 
 ils_encryption_key = "3e2ac9b644dd4e0b64b179a0309ead13bb59202a"
+
+; OpenURL configuration institution specified
+; sample configuration for ubl
+[OpenURL]
+url             = "http://www.redi-bw.de/links/ubl?rl_site=ubl"
+rfr_id          = www.ub.uni-leipzig.de
+resolver        = redi
+window_settings = "toolbar=no,location=no,directories=no,buttons=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=550,height=600"
+show_in_results = false    ; include in search results
+show_in_record = false     ; include in core record metadata
+show_in_holdings = true    ; include in holdings tab of record view
+embed = true
+replace_other_urls = true
diff --git a/local/languages/de.ini b/local/languages/de.ini
index 849f10adf3b..4c6b333c40c 100644
--- a/local/languages/de.ini
+++ b/local/languages/de.ini
@@ -1817,4 +1817,7 @@ Zweigbibliothek = Branch Library
 Fachgebiet = Subject
 history = Search History
 renew_item_maximum = There is a maximum of three renewals allowed.
-guser_dunning_process = User account is blocked due to a dunning process
\ No newline at end of file
+guser_dunning_process = User account is blocked due to a dunning process
+
+################### finc-spezifisch inzugefuegt ##########################
+p. = S.
\ No newline at end of file
diff --git a/local/languages/en.ini b/local/languages/en.ini
index 332ac43c3a1..e29e8218f40 100644
--- a/local/languages/en.ini
+++ b/local/languages/en.ini
@@ -1761,3 +1761,7 @@ Check interlibrary loans = Check interlibrary loans
 Check availability via interlibrary loans = Check availability via interlibrary loans
 Initiate purchase order = Initiate purchase order
 Please purchase = Please purchase
+
+
+################### finc-spezifisch hinzugefuegt ##########################
+p. = p.
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Solr/MultiIndexListener.php b/module/VuFind/src/VuFind/Search/Solr/MultiIndexListener.php
index 36502f9cf85..1620d1a8c75 100644
--- a/module/VuFind/src/VuFind/Search/Solr/MultiIndexListener.php
+++ b/module/VuFind/src/VuFind/Search/Solr/MultiIndexListener.php
@@ -124,7 +124,11 @@ class MultiIndexListener
             } else {
                 // In any other context, we should make sure our field values are
                 // all legal.
-                $shards = explode(',', implode(',', $params->get('shards')));
+
+                // Check if $params->get('shards') returns an array to prevent
+                // invalid argument warnings.
+                $shards = explode(',', implode(',',
+                    (is_array($params->get('shards')) ? $params->get('shards') : array())));
                 $fields = $this->getFields($shards);
                 $specs  = $this->getSearchSpecs($fields);
                 $backend->getQueryBuilder()->setSpecs($specs);
diff --git a/module/VuFind/src/VuFind/Search/Solr/Params.php b/module/VuFind/src/VuFind/Search/Solr/Params.php
index f08961a0e9e..861d6df6b70 100644
--- a/module/VuFind/src/VuFind/Search/Solr/Params.php
+++ b/module/VuFind/src/VuFind/Search/Solr/Params.php
@@ -459,7 +459,7 @@ class Params extends \VuFind\Search\Base\Params
         // Shards
         $allShards = $this->getOptions()->getShards();
         $shards = $this->getSelectedShards();
-        if (is_null($shards)) {
+        if (empty($shards)) {
             $shards = array_keys($allShards);
         }
 
diff --git a/module/finc/config/module.config.php b/module/finc/config/module.config.php
index db891c69720..1932dc217f5 100644
--- a/module/finc/config/module.config.php
+++ b/module/finc/config/module.config.php
@@ -18,7 +18,13 @@ $config = array(
                     'solrdefault' => 'finc\RecordDriver\Factory::getSolrDefault',
                     'solrmarc' => 'finc\RecordDriver\Factory::getSolrMarc',
                     'solrmarcremote' => 'finc\RecordDriver\Factory::getSolrMarcRemote',
-                    'solrmarcremotefinc' => 'finc\RecordDriver\Factory::getSolrMarcRemoteFinc'
+                    'solrmarcremotefinc' => 'finc\RecordDriver\Factory::getSolrMarcRemoteFinc',
+                    'solrai' => 'finc\RecordDriver\Factory::getSolrAI',
+                ),
+            ),
+            'resolver_driver' => array(
+                'factories' => array(
+                    'redi' => 'finc\Resolver\Driver\Factory::getRedi',
                 ),
             ),
         ),
@@ -62,6 +68,16 @@ $config = array(
                     'Preview' => 'preview',
                     'HierarchyTree' => 'HierarchyTree', 'Map' => 'Map',
                     'Details' => 'StaffViewMARC',
+				),
+                'defaultTab' => null,
+            ),
+            'finc\RecordDriver\SolrAI' => array(
+                'tabs' => array (
+                    'Holdings' => 'HoldingsILS', 'Description' => 'Description',
+                    'TOC' => 'TOC', 'UserComments' => 'UserComments',
+                    'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
+                    'HierarchyTree' => 'HierarchyTree', 'Map' => 'Map',
+                    'Details' => 'StaffViewArray',
                 ),
                 'defaultTab' => null,
             ),
diff --git a/module/finc/src/finc/RecordDriver/Factory.php b/module/finc/src/finc/RecordDriver/Factory.php
index 7579e829f1b..1336afd0ad0 100644
--- a/module/finc/src/finc/RecordDriver/Factory.php
+++ b/module/finc/src/finc/RecordDriver/Factory.php
@@ -63,6 +63,22 @@ class Factory
         return $driver;
     }
 
+    /**
+     * Factory for SolrAI record driver.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return SolrAI
+     */
+    public static function getSolrAI(ServiceManager $sm)
+    {
+        return new SolrAI(
+            $sm->getServiceLocator()->get('VuFind\Config')->get('config'),
+            $sm->getServiceLocator()->get('VuFind\Config')->get('SolrAI'),
+            null
+        );
+    }
+
     /**
      * Factory for SolrMarcRemoteFinc record driver.
      *
diff --git a/module/finc/src/finc/RecordDriver/SolrAI.php b/module/finc/src/finc/RecordDriver/SolrAI.php
new file mode 100644
index 00000000000..b766d7167a2
--- /dev/null
+++ b/module/finc/src/finc/RecordDriver/SolrAI.php
@@ -0,0 +1,867 @@
+<?php
+/**
+ * Recorddriver for Solr records from the aggregated index of Leipzig University
+ * Library
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Leipzig University Library 2015.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  RecordDrivers
+ * @author   André Lahmann <lahmann@ub.uni-leipzig.de>
+ * @author   Gregor Gawol <gawol@ub.uni-leipzig.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:record_drivers Wiki
+ */
+namespace finc\RecordDriver;
+
+/**
+ * Recorddriver for Solr records from the aggregated index of Leipzig University
+ * Library
+ *
+ * @category VuFind2
+ * @package  RecordDrivers
+ * @author   André Lahmann <lahmann@ub.uni-leipzig.de>
+ * @author   Gregor Gawol <gawol@ub.uni-leipzig.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:record_drivers Wiki
+ * @SuppressWarnings(PHPMD.ExcessivePublicCount)
+ */
+class SolrAI extends SolrDefault
+{
+    /**
+     * Logger (or false for none)
+     *
+     * @var LoggerInterface|bool
+     */
+    protected $logger = false;
+
+    /**
+     * AI record
+     *
+     * @var array
+     */
+    protected $aiRecord;
+
+    /**
+     * holds config.ini data
+     *
+     * @var array
+     */
+    protected $mainConfig;
+
+
+    /**
+     * gets the description of the record
+     *
+     * @return string description
+     */
+    public function getDescriptions()
+    {
+        return $this->_getAIFullrecordArrayValue('abstract');
+    }
+
+    /**
+     * gets the edition key from the record
+     *
+     * @return string edition
+     */
+    public function getEdition()
+    {
+        return $this->_getAIFullrecordStringValue('rft.edition');
+    }
+
+    /**
+     * gets the publication date of the record
+     *
+     * @return string publication date
+     */
+    public function getDate()
+    {
+        return isset($this->fields['publishDateSort']) ?
+            $this->fields['publishDateSort'] : '';
+    }
+
+    /**
+     * gets an array of issues from record
+     *
+     * @return array of issues
+     */
+    public function getIssues()
+    {
+        return $this->_getAIFullrecordStringValue('rft.issue');
+    }
+
+
+    /**
+     * Get an array of publication detail of first entry combined from
+     * place, publisher and data.
+     *
+     * @return array
+     * @access protected
+     */
+    /*public function getFirstPublicationDetails()
+    {
+        $place = $this->getPlacesOfPublication();
+        $date = $this->getPublicationDates();
+        $array = array();
+        if (isset($this->fields['format']) && is_array($this->fields['format'])) {
+            $array['issue'] = (isset($this->fields['hierarchy_parent_title']) ?
+                'In: '.$this->fields['hierarchy_parent_title'][0] : '');
+            $array['date'] = ((is_array($date) && (count($date) > 0) ?
+                $date[0] : ''));
+            switch ((count($this->fields['format']) > 0) ?
+                $this->fields['format'][0] : '') {
+                case 'eBook':
+                    $array['place'] = ((is_array($place) && (count($place) > 0) ?
+                        $place[0] : ''));
+                    break;
+                case 'ElectronicArticle':
+                    $array['place'] = '';
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return $array;
+    }*/
+
+    /**
+     * Has FirstPublicationsDetails a Date in it
+     *
+     * @return boolean
+     * @access protected
+     */
+    protected function getIsPublicationDetailsDate()
+    {
+        return true;
+    }
+
+    /**
+     * Get the main author of the record.
+     *
+     * @return string
+     * @access protected
+     */
+    public function getPrimaryAuthor()
+    {
+        return null;
+    }
+
+    /**
+     * Get additional entries for personal names.
+     *
+     * @return array
+     * @access protected
+     * @link http://www.loc.gov/marc/bibliographic/bd700.html
+     */
+    protected function getAdditionalAuthors()
+    {
+        if (isset($this->record['authors'])
+            && is_array($this->record['authors'])
+            && (count($this->record['authors']) > 0)
+        ) {
+            $retval = array();
+            $i = 0;
+            $authors = $this->record['authors'];
+            foreach ($authors as $value) {
+                $retval[$i]['name'] = (isset($value['rft.aulast']) ?
+                        $value['rft.aulast'].', ' : '')
+                    .(isset($value['rft.aufirst']) ? $value['rft.aufirst'] : '');
+                $i++;
+            }
+            return $retval;
+        }
+        return array();
+    }
+
+    /**
+     * Get the title of the item that contains this record (i.e. MARC 773s of a
+     * journal).
+     *
+     * @return string
+     */
+    public function getContainerTitle()
+    {
+        return (isset($this->fields['hierarchy_parent_title']) ?
+                $this->fields['hierarchy_parent_title'][0] : '');
+    }
+
+    /**
+     * Get an array of publication detail lines combining information from
+     * getPublicationDates(), getPublishers() and getPlacesOfPublication().
+     *
+     * @return array
+     * @access protected
+     */
+    public function getPublicationDetails()
+    {
+        $names =  $this->_getAIFullrecordArrayValue('rft.pub');
+        $i = 0;
+        $retval = array();
+        while (isset($names[$i])) {
+            // Build objects to represent each set of data; these will
+            // transform seamlessly into strings in the view layer.
+            $retval[] = new \VuFind\RecordDriver\Response\PublicationDetails(
+                null,
+                isset($names[$i]) ? $names[$i] : '',
+                null
+            );
+            $i++;
+        }
+        return $retval;
+    }
+
+    /**
+     * Get the publication dates of the record.  See also getDateSpan().
+     *
+     * @return array
+     */
+    public function getPublicationDates()
+    {
+        return isset($this->fields['publishDateSort']) ?
+            $this->fields['publishDateSort'] : '';
+    }
+
+    /**
+     * Returns an array with the necessary information to create a detailed
+     * "Published in" line in RecordDriver core.phtml
+     *
+     * @return array
+     */
+    public function getAIDataIn()
+    {
+        return array(
+            'jtitle' => $this->getJTitle(),
+            'volume' => $this->getVolume(),
+            'date'   => $this->getDate(),
+            'issue'  => $this->getIssues(),
+            'issns'  => $this->getISSNs(),
+            'pages'  => $this->getPages()
+        );
+    }
+
+    /**
+     * gets an array of series from record
+     *
+     * @return array of series
+     */
+    public function getSeries()
+    {
+        return $this->_getAIFullrecordArrayValue('rft.series');
+    }
+
+    /**
+     * gets an array of volumes from record
+     *
+     * @return array of volumes
+     */
+    public function getVolume()
+    {
+        return $this->_getAIFullrecordStringValue('rft.volume');
+    }
+
+    /**
+     * Get the ISSN from a record.
+     *
+     * @return array
+     * @access protected
+     * @link https://intern.finc.info/fincproject/issues/969 description
+     */
+    public function getISSNs()
+    {
+        return $this->_getAIFullrecordArrayValue('rft.issn');
+    }
+
+    /**
+     * Get the eISSN from a record.
+     *
+     * @return array
+     * @access protected
+     * @link https://intern.finc.info/fincproject/issues/969 description
+     */
+    public function getEISSNs()
+    {
+        return $this->_getAIFullrecordArrayValue('rft.eissn');
+    }
+
+    /**
+     * Get an array of all ISSNs associated with the record (may be empty).
+     * Can be the main ISSN and the parent ISSNs.
+     *
+     * @return array
+     * @access protected
+     */
+    public function getISBNs()
+    {
+        return $this->_getAIFullrecordArrayValue('rft.isbn');
+    }
+
+    /**
+     * gets pages as 'start - end' if both exist
+     *
+     * @return string pages
+     */
+    public function getPages()
+    {
+        if ($this->hasStartpages()
+            && $this->hasEndpages()
+        ) {
+            return sprintf(
+                '%s - %s',
+                $this->aiRecord['rft.spage'],
+                $this->aiRecord['rft.epage']
+            );
+        } else if ($this->hasStartpages()) {
+            return $this->aiRecord['rft.spage'][0];
+        } else if ($this->hasEndpages()) {
+            return $this->aiRecord['rft.epage'][0];
+        }
+    }
+
+    /**
+     * Return an associative array of URLs associated with this record (key = URL,
+     * value = description).
+     *
+     * @return array
+     * @access protected
+     */
+    public function getURLs()
+    {
+        $rediUrl = $this->mainConfig->OpenURL->rediUrl;
+        if (isset($rediUrl)) {
+            $urls = array();
+            $url = sprintf($rediUrl, $this->getOpenURL());
+            $filter = function ($url) {
+                return array('url' => $url, 'desc' => (strlen($url) > 200) ?
+                    $this->translate('full text') : $this->translate($url));
+            };
+            $urls[] = $url;
+            return array_map($filter, $urls);
+        }
+        return array();
+    }
+
+    /**
+     * Return the jtitle field of ai records
+     *
+     * @return array   Return jtitle fields.
+     * @access public
+     */
+    public function getJTitle ()
+    {
+        return $this->_getAIFullrecordStringValue('rft.jtitle');
+    }
+
+    /**
+     * Return the jtitle field of ai records
+     *
+     * @return array   Return jtitle fields.
+     * @access public
+     */
+    public function getATitle ()
+    {
+        return $this->_getAIFullrecordStringValue('rft.atitle');
+    }
+
+    /**
+     * Return the jtitle field of ai records
+     *
+     * @return array   Return jtitle fields.
+     * @access public
+     */
+    public function getBTitle ()
+    {
+        return $this->_getAIFullrecordStringValue('rft.btitle');
+    }
+
+    /**
+     * Get the OpenURL parameters to represent this record (useful for the
+     * title attribute of a COinS span tag).
+     *
+     * @return string OpenURL parameters.
+     */
+    public function getOpenURL() {
+        // Set up parameters based on the format of the record:
+        switch ($this->aiRecord['rft.genre']) {
+            case 'book':
+                $params = $this->getBookOpenURLParams();
+                break;
+            case 'article':
+                $params = $this->getArticleOpenURLParams();
+                break;
+            case 'journal':
+                $params = $this->getJournalOpenURLParams();
+                break;
+            default:
+                $format = $this->getFormats();
+                $params = $this->getUnknownFormatOpenURLParams($format);
+                break;
+        }
+
+        // Assemble the URL:
+        return http_build_query($params);
+    }
+
+    /**
+     * Get the COinS identifier.
+     *
+     * @return string
+     */
+    protected function getCoinsID()
+    {
+        // Get the COinS ID -- it should be in the OpenURL section of config.ini,
+        // but we'll also check the COinS section for compatibility with legacy
+        // configurations (this moved between the RC2 and 1.0 releases).
+        if (isset($this->mainConfig->OpenURL->rfr_id)
+            && !empty($this->mainConfig->OpenURL->rfr_id)
+        ) {
+            return $this->mainConfig->OpenURL->rfr_id;
+        }
+        return 'vufind.svn.sourceforge.net';
+    }
+
+    /**
+     * Get default OpenURL parameters.
+     *
+     * @return array
+     */
+    protected function getDefaultOpenURLParams()
+    {
+        // Start an array of OpenURL parameters:
+        return array(
+            'ctx_ver' => 'Z39.88-2004',
+            'ctx_enc' => 'info:ofi/enc:UTF-8',
+            'rfr_id' => 'info:sid/' . $this->getCoinsID() . ':generator'
+        );
+    }
+
+    /**
+     * Get OpenURL parameters for an article.
+     *
+     * @return array
+     */
+    protected function getArticleOpenURLParams()
+    {
+        $params = $this->getDefaultOpenURLParams();
+        // unset default title -- we only want jtitle/atitle here:
+        //$params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:journal';
+        $params['genre'] = 'article';
+        if (isset($this->aiRecord['finc.record_id'])) {
+            $params['rft_id'] = $this->aiRecord['finc.record_id'];
+        }
+        if (isset($this->aiRecord['rft.issn'])) {
+            foreach ($this->aiRecord['rft.issn'] as $issn) {
+                $params['issn'] = $issn;
+            }
+        }
+        // an article may have also an ISBN:
+        if (isset($this->aiRecord['rft.isbn'])) {
+            $params['isbn'] = $this->aiRecord['rft.isbn'];
+        }
+        if (isset($this->aiRecord['rft.ssn'])) {
+            $params['ssn'] = $this->aiRecord['rft.ssn'];
+        }
+        if (isset($this->aiRecord['rft.volume'])) {
+            $params['volume'] = $this->aiRecord['rft.volume'];
+        }
+        if (isset($this->aiRecord['rft.issue'])) {
+            $params['issue'] = $this->aiRecord['rft.issue'];
+        }
+        if (isset($this->aiRecord['rft.spage'])) {
+            $params['spage'] = $this->aiRecord['rft.spage'];
+        }
+        if (isset($this->aiRecord['rft.epage'])) {
+            $params['epage'] = $this->aiRecord['rft.epage'];
+        }
+        if (isset($this->aiRecord['rft.pages'])) {
+            $params['pages'] = $this->aiRecord['rft.pages'];
+        }
+        if (isset($this->aiRecord['rft.coden'])) {
+            $params['coden'] = $this->aiRecord['rft.coden'];
+        }
+        if (isset($this->aiRecord['rft.artnum'])) {
+            $params['artnum'] = $this->aiRecord['rft.artnum'];
+        }
+        if (isset($this->aiRecord['rft.sici'])) {
+            $params['sici'] = $this->aiRecord['rft.sici'];
+        }
+        if (isset($this->aiRecord['rft.chron'])) {
+            $params['chron'] = $this->aiRecord['rft.chron'];
+        }
+        if (isset($this->aiRecord['rft.quarter'])) {
+            $params['quarter'] = $this->aiRecord['rft.quarter'];
+        }
+        if (isset($this->aiRecord['rft.part'])) {
+            $params['part'] = $this->aiRecord['rft.part'];
+        }
+        if (isset($this->aiRecord['rft.jtitle'])) {
+            $params['jtitle'] = $this->getJTitle();
+        }
+        if (isset($this->aiRecord['rft.atitle'])) {
+            $params['atitle'] = $this->getATitle();
+        }
+        if (isset($this->aiRecord['rft.stitle'])) {
+            $params['stitle'] = $this->aiRecord['rft.stitle'];
+        }
+        if (isset($this->aiRecord['authors'])) {
+            foreach ($this->aiRecord['authors'] as $author) {
+                if (isset($author['rft.au'])) {
+                    $params['au'] = $author['rft.au'];
+                }
+                if (isset($author['rft.aulast'])) {
+                    $params['aulast'] = $author['rft.aulast'];
+                }
+                if (isset($author['rft.aucorp'])) {
+                    $params['aucorp'] = $author['rft.aucorp'];
+                }
+                if (isset($author['rft.auinitm'])) {
+                    $params['auinitm'] = $author['rft.auinitm'];
+                }
+                if (isset($author['rft.aufirst'])) {
+                    $params['aufirst'] = $author['rft.aufirst'];
+                }
+                if (isset($author['rft.auinit'])) {
+                    $params['auinit'] = $author['rft.auinit'];
+                }
+                if (isset($author['rft.auinit1'])) {
+                    $params['auinit1'] = $author['rft.auinit1'];
+                }
+                if (isset($author['rft.ausuffix'])) {
+                    $params['ausuffix'] = $author['rft.ausuffix'];
+                }
+            }
+        }
+
+        if (isset($this->aiRecord['rft.format'])) {
+            $params['format'] = $this->aiRecord['rft.format'];
+        }
+        if (isset($this->aiRecord['doi'])) {
+            $params['rft_id'] = 'info:doi/'.$this->aiRecord['doi'];
+        }
+        if (isset($this->aiRecord['languages'])) {
+            $params['rft.language'] = $this->aiRecord['languages'];
+        }
+        if (isset($this->aiRecord['rft.date'])) {
+            $params['rft.date'] = $this->aiRecord['rft.date'];
+        }
+        return $params;
+    }
+
+    /**
+     * Get OpenURL parameters for a book.
+     *
+     * @return array
+     */
+    protected function getBookOpenURLParams()
+    {
+        $params = $this->getDefaultOpenURLParams();
+        $params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:book';
+        $params['genre'] = 'book';
+        if (isset($this->aiRecord['rft.atitle'])) {
+            $params['atitle'] = $this->getATitle();
+        }
+        if (isset($this->aiRecord['rft.btitle'])) {
+            $params['rft.btitle'] = $this->getBTitle();
+        }
+        if (isset($this->aiRecord['finc.record_id'])) {
+            $params['rft_id'] = $this->aiRecord['finc.record_id'];
+        }
+        if (isset($this->aiRecord['rft.issn'])) {
+            foreach ($this->aiRecord['rft.issn'] as $issn) {
+                $params['issn'] = $issn;
+            }
+        }
+        if (isset($this->aiRecord['rft.edition'])) {
+            $params['edition'] = $this->aiRecord['rft.edition'];
+        }
+        // an article may have also an ISBN:
+        if (isset($this->aiRecord['rft.isbn'])) {
+            $params['isbn'] = $this->aiRecord['rft.isbn'];
+        }
+        if (isset($this->aiRecord['rft.ssn'])) {
+            $params['ssn'] = $this->aiRecord['rft.ssn'];
+        }
+        if (isset($this->aiRecord['rft.eissn'])) {
+            $params['eissn'] = $this->aiRecord['rft.eissn'];
+        }
+        if (isset($this->aiRecord['rft.volume'])) {
+            $params['volume'] = $this->aiRecord['rft.volume'];
+        }
+        if (isset($this->aiRecord['rft.issue'])) {
+            $params['issue'] = $this->aiRecord['rft.issue'];
+        }
+        if (isset($this->aiRecord['rft.spage'])) {
+            $params['spage'] = $this->aiRecord['rft.spage'];
+        }
+        if (isset($this->aiRecord['rft.epage'])) {
+            $params['epage'] = $this->aiRecord['rft.epage'];
+        }
+        if (isset($this->aiRecord['rft.pages'])) {
+            $params['pages'] = $this->aiRecord['rft.pages'];
+        }
+        if (isset($this->aiRecord['rft.series'])) {
+            $params['series'] = $this->aiRecord['rft.series'];
+        }
+        if (isset($this->aiRecord['rft.tpages'])) {
+            $params['tpages'] = $this->aiRecord['rft.tpages'];
+        }
+        if (isset($this->aiRecord['rft.bici'])) {
+            $params['bici'] = $this->aiRecord['rft.bici'];
+        }
+        if (isset($this->aiRecord['authors'])) {
+            foreach ($this->aiRecord['authors'] as $author) {
+                if (isset($author['rft.au'])) {
+                    $params['au'] = $author['rft.au'];
+                }
+                if (isset($author['rft.aulast'])) {
+                    $params['aulast'] = $author['rft.aulast'];
+                }
+                if (isset($author['rft.aucorp'])) {
+                    $params['aucorp'] = $author['rft.aucorp'];
+                }
+                if (isset($author['rft.auinitm'])) {
+                    $params['auinitm'] = $author['rft.auinitm'];
+                }
+                if (isset($author['rft.aufirst'])) {
+                    $params['aufirst'] = $author['rft.aufirst'];
+                }
+                if (isset($author['rft.auinit'])) {
+                    $params['auinit'] = $author['rft.auinit'];
+                }
+                if (isset($author['rft.auinit1'])) {
+                    $params['auinit1'] = $author['rft.auinit1'];
+                }
+                if (isset($author['rft.ausuffix'])) {
+                    $params['ausuffix'] = $author['rft.ausuffix'];
+                }
+            }
+        }
+
+        if (isset($this->aiRecord['rft.format'])) {
+            $params['format'] = $this->aiRecord['rft.format'];
+        }
+        if (isset($this->aiRecord['doi'])) {
+            $params['rft_id'] = 'info:doi/'.$this->aiRecord['doi'];
+        }
+        $publishers = $this->getPublishers();
+        if (count($publishers) > 0) {
+            $params['rft.pub'] = $publishers[0];
+        }
+        return $params;
+    }
+
+    /**
+     * Retrieve raw data from object (primarily for use in staff view and
+     * autocomplete; avoid using whenever possible).
+     *
+     * @return mixed
+     */
+    public function getRawData()
+    {
+        $tmp = array();
+        $i = 0;
+        if (!empty($this->aiRecord)) {
+            foreach ($this->aiRecord as $key => $value) {
+                $tmp[$i]['key'] = $key;
+                $tmp[$i]['value'] = $value;
+                $i++;
+            }
+        }
+        return $tmp;
+    }
+
+    /**
+     * Retrieve data from ai-blobserver
+     *
+     * @param string $id      Record Id of the raw recorddata to be retrieved
+     * @param string $baseUrl The Ai fullrecord server url.
+     *
+     * @return mixed          Raw curl request response (should be json).
+     * @throws Exception
+     */
+    protected function retrieveAiFullrecord($id, $baseUrl)
+    {
+        if (!isset($id)) {
+            throw new Exception('no id given');
+        }
+
+        $url = sprintf($baseUrl, $id);
+
+        $curlDefaultOptions = array(
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_SSL_VERIFYPEER => true,
+            CURLOPT_USERAGENT => "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)"
+        );
+        $curlOptions = array();
+        $curlOptions[CURLOPT_URL] = $url;
+        $handle = curl_init();
+        $curl_options = $curlDefaultOptions + $curlOptions;
+        curl_setopt_array($handle, $curl_options);
+        $response = curl_exec($handle);
+        if (false === $response) {
+            throw new Exception(curl_error($handle));
+        }
+        return $response;
+    }
+
+    /**
+     * Returns the AI fullrecord as decoded json.
+     *
+     * @param string $id Record id to be retrieved.
+     *
+     * @return array
+     * @throws \Exception
+     */
+    protected function getAIJSONFullrecord($id)
+    {
+        if (!isset($this->recordConfig->General)) {
+            throw new \Exception('SolrAI General settings missing.');
+        }
+
+        $baseUrl = $this->recordConfig->General->baseUrl;
+
+        if (!isset($baseUrl)) {
+            throw new \Exception('no ai-blobserver configurated');
+        }
+
+        $response = $this->retrieveAiFullrecord($id, $baseUrl);
+
+        return json_decode($response, true);
+    }
+
+    /**
+     * returns the value of a certain record key or the default value if not exists
+     *
+     * @param string $key     of record array
+     * @param mixed  $default [optional] return value
+     *
+     * @return mixed value of key
+     * @access private
+     */
+    private function _getAIFullrecordStringValue($key, $default = '')
+    {
+        if (!$this->_hasAIFullrecordStringValue($key)) {
+            if ($this->_hasAIFullrecordArrayValue($key)) {
+                return implode(',', $this->aiRecord[$key]);
+            }
+            return $default;
+        }
+
+        return $this->aiRecord[$key];
+    }
+
+    /**
+     * returns the value of a certain record key or the default value if not exists
+     *
+     * @param string $key     of record array
+     * @param mixed  $default [optional] return value
+     *
+     * @return mixed value of key
+     * @access private
+     */
+    private function _getAIFullrecordArrayValue($key, $default = array())
+    {
+        if (!$this->_hasAIFullrecordArrayValue($key)) {
+            return $default;
+        }
+        return $this->aiRecord[$key];
+    }
+
+    /**
+     * checks whether a certain array key exists and is not empty in record data array
+     *
+     * @param string $key Key to be checked.
+     *
+     * @return boolean true or false
+     * @access private
+     */
+    private function _hasAIFullrecordStringValue($key)
+    {
+        if (empty($this->aiRecord)) {
+            $this->aiRecord = $this->getAIJSONFullrecord($this->fields['id']);
+        }
+        if (isset($this->aiRecord[$key])
+            && !empty($this->aiRecord[$key])
+            && !is_array($this->aiRecord[$key])
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * checks whether a certain array key exists, is an array and has elements in
+     * record data array
+     *
+     * @param string $key Key to be checked
+     *
+     * @return boolean true or false
+     * @access private
+     */
+    private function _hasAIFullrecordArrayValue($key)
+    {
+        if (empty($this->aiRecord)) {
+            $this->aiRecord = $this->getAIJSONFullrecord($this->fields['id']);
+        }
+        if (isset($this->aiRecord[$key])
+            && is_array($this->aiRecord[$key])
+            && count($this->aiRecord[$key]) > 0
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * checks whether record has start pages
+     *
+     * @return boolean
+     */
+    public function hasStartpages()
+    {
+        if (isset($this->aiRecord['rft.spage'])
+            && !empty($this->aiRecord['rft.spage'])
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * checks whether record has Endpages
+     *
+     * @return boolean
+     */
+    public function hasEndpages()
+    {
+        if (isset($this->aiRecord['rft.epage'])
+            && !empty($this->aiRecord['rft.epage'])
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+}
diff --git a/module/finc/src/finc/RecordDriver/SolrDefault.php b/module/finc/src/finc/RecordDriver/SolrDefault.php
index 0b84d2a28ca..672cd5abc89 100644
--- a/module/finc/src/finc/RecordDriver/SolrDefault.php
+++ b/module/finc/src/finc/RecordDriver/SolrDefault.php
@@ -100,7 +100,6 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * @deprecated      Should also be possible to be dropped (@see getLocalFormat())
      *
      * @return array
-     * @access protected
      */
     public function getFormat()
     {
@@ -113,7 +112,6 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * format_{indexExtension}, format otherwise.
      *
      * @return array        Array with formats associated with the record.
-     * @access protected
      */
     public function getFormats()
     {
@@ -144,7 +142,6 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * @todo                Should be moved out of RecordDriver (Controller/View?)
      *
      * @return array
-     * @access protected
      */
 /*    protected function getFormatIcon()
     {
@@ -186,9 +183,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get the GND of an author.
      *
      * @return array
-     * @access protected
      */
-    protected function getAuthorId()
+    public function getAuthorId()
     {
         return isset($this->fields['author_id']) ?
             $this->fields['author_id'] : array();
@@ -200,10 +196,9 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * @todo    Check whether static call of getCorporateAuthor is necessary
      *
      * @return array
-     * @access protected
      * @link https://intern.finc.info/issues/1866
      */
-    protected function getCombinedAuthors()
+    public function getCombinedAuthors()
     {
         $retval = array();
 
@@ -233,9 +228,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get the original author of the record.
      *
      * @return string
-     * @access protected
      */
-    protected function getPrimaryAuthorOrig()
+    public function getPrimaryAuthorOrig()
     {
         return isset($this->fields['author_orig']) ?
             $this->_filterAuthorDates($this->fields['author_orig']) : '';
@@ -245,10 +239,9 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get the main author of the record.
      *
      * @return string
-     * @access protected
      * @deprecated
      */
-    protected function getPrimaryAuthorRaw()
+    public function getPrimaryAuthorRaw()
     {
         return isset($this->fields['author']) ?
             $this->_removeAuthorDates($this->fields['author']) : '';
@@ -270,9 +263,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get the secondary corporate authors (if any) for the record.
      *
      * @return array
-     * @access protected
      */
-    protected function getCorporateSecondaryAuthors()
+    public function getCorporateSecondaryAuthors()
     {
         return isset($this->fields['author_corp2']) ?
             $this->fields['author_corp2'] : array();
@@ -282,9 +274,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get an array of all ISMNs associated with the record (may be empty).
      *
      * @return array
-     * @access protected
      */
-    protected function getISMNs()
+    public function getISMNs()
     {
         return isset($this->fields['ismn']) && is_array($this->fields['ismn']) ?
             $this->fields['ismn'] : array();
@@ -294,9 +285,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get an array of newer titles for the record.
      *
      * @return array
-     * @access protected
      */
-    protected function getNewTitles()
+    public function getNewTitles()
     {
         return isset($this->fields['title_new']) ?
             $this->fields['title_new'] : array();
@@ -310,11 +300,10 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * @todo                    1. Check if this method is still needed
      * @todo                    2. Refactor Solr-Query to be compatible with VuFind2
      *
-     * @param array $rids       Array of record ids to test.
+     * @param array $rids Array of record ids to test.
      *
-     * @return int mixed        If success return at least one finc id otherwise null.
-     * @access protected
-     * @deprecated              Not used.
+     * @return int mixed  If success return at least one finc id otherwise null.
+     * @deprecated        Not used.
      */
     protected function addFincIDToRecord ( $array ) {
 /*
@@ -390,10 +379,9 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get percentage of relevance of a title. First implementaion for TUBAF.
      *
      * @return float        Percentage of Score / Maximum Score rounded by 5.
-     * @access protected
      * @link https://intern.finc.info/issues/1908
      */
-    protected function getRelevance() {
+    public function getRelevance() {
 
         $score = isset($this->fields['score']) ?  $this->fields['score'] : 0;
         $maxScore = isset($this->fields['score_maximum']) ? $this->fields['score_maximum'] : 0;
@@ -408,9 +396,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get RVK classifcation number from Solr index.
      *
      * @return string
-     * @access protected
      */
-    protected function getRvk() {
+    public function getRvk() {
         return isset($this->fields['rvk_facet']) ?
             $this->fields['rvk_facet'] : '';
     }
@@ -421,7 +408,6 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * @todo    refactor to a more meaningful name?
      *
      * @return string
-     * @access protected
      */
     public function getRID()
     {
@@ -433,9 +419,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get the original title of the record.
      *
      * @return string
-     * @access protected
      */
-    protected function getTitleOrig()
+    public function getTitleOrig()
     {
         return isset($this->fields['title_orig']) ?
             $this->fields['title_orig'] : '';
@@ -445,9 +430,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get the GND of topic.
      *
      * @return array
-     * @access protected
      */
-    protected function getTopicId()
+    public function getTopicId()
     {
         return isset($this->fields['topic_id']) ?
             $this->fields['topic_id'] : array();
@@ -457,9 +441,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get alternatives series titles as array.
      *
      * @return array
-     * @access protected
      */
-    protected function getSeriesAlternative()
+    public function getSeriesAlternative()
     {
         if (isset($this->fields['series2']) && !empty($this->fields['series2'])) {
             return $this->fields['series2'];
@@ -471,9 +454,8 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Get alternatives series titles as array.
      *
      * @return array
-     * @access protected
      */
-    protected function getSeriesOrig()
+    public function getSeriesOrig()
     {
         if (isset($this->fields['series_orig']) && !empty($this->fields['series_orig'])) {
             return $this->fields['series_orig'];
@@ -485,11 +467,11 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * Filter author data for author year of birth and death
      * to give a better mark up.
      *
-     * @param string authordata
+     * @param string $authordata
+     *
      * @return strings
-     * @access protected
      */
-    protected function _filterAuthorDates( $authordata )
+    private function _filterAuthorDates( $authordata )
     {
         if (preg_match('/^(\s|.*)(\d{4})\s?-?\s?(\d{4})?$/Uu',$authordata, $match)) {
             return (isset($match[3]))
@@ -505,10 +487,9 @@ class SolrDefault extends \VuFind\RecordDriver\SolrDefault
      * @param string authordata
      *
      * @return strings
-     * @access protected
      * @deprecated
      */
-    protected function _removeAuthorDates( $authordata )
+    private function _removeAuthorDates( $authordata )
     {
         if (preg_match('/^(\s|.*)\s(fl.\s|d.\s|ca.\s)*\s?(\d{4})\??(\sor\s\d\d?)?\s?(-|–)?\s?(ca.\s|after\s)?(\d{1,4})?(.|,)?$/Uu',$authordata, $match)) {
             return (isset($match[1])) ? $match[1] : $authordata;
diff --git a/module/finc/src/finc/Resolver/Driver/Factory.php b/module/finc/src/finc/Resolver/Driver/Factory.php
new file mode 100644
index 00000000000..f17535739c6
--- /dev/null
+++ b/module/finc/src/finc/Resolver/Driver/Factory.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Resolver Driver Factory Class
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2014.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Resolver_Drivers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+namespace finc\Resolver\Driver;
+use Zend\ServiceManager\ServiceManager;
+
+/**
+ * Resolver Driver Factory Class
+ *
+ * @category VuFind2
+ * @package  Resolver_Drivers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+class Factory
+{
+
+    /**
+     * Factory for Redi record driver.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return Redi
+     */
+    public static function getRedi(ServiceManager $sm)
+    {
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        return new Redi(
+            $config->OpenURL->url,
+            $sm->getServiceLocator()->get('VuFind\Http')->createClient()
+        );
+    }
+}
\ No newline at end of file
diff --git a/module/finc/src/finc/Resolver/Driver/Redi.php b/module/finc/src/finc/Resolver/Driver/Redi.php
new file mode 100644
index 00000000000..cb38f91904f
--- /dev/null
+++ b/module/finc/src/finc/Resolver/Driver/Redi.php
@@ -0,0 +1,318 @@
+<?php
+/**
+ * ReDi Link Resolver Driver
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Leipzig University Library 2015
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Resolver_Drivers
+ * @author   Gregor Gawol <gawol@ub.uni-leipzig.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:link_resolver_drivers Wiki
+ */
+namespace finc\Resolver\Driver;
+use DOMDocument;
+
+/**
+ * ReDi Link Resolver Driver
+ *
+ * @category VuFind2
+ * @package  Resolver_Drivers
+ * @author   Gregor Gawol <gawol@ub.uni-leipzig.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:link_resolver_drivers Wiki
+ */
+class Redi implements \VuFind\Resolver\Driver\DriverInterface
+{
+    /**
+     * Base URL for link resolver
+     *
+     * @var string
+     */
+    protected $baseUrl;
+
+    /**
+     * HTTP client
+     *
+     * @var \Zend\Http\Client
+     */
+    protected $httpClient;
+
+    /**
+     * Base URL for link resolver
+     *
+     * @var string
+     */
+    protected $doc;
+
+    /**
+     * Constructor
+     *
+     * @param string            $baseUrl    Base URL for link resolver
+     * @param \Zend\Http\Client $httpClient HTTP client
+     */
+    public function __construct($baseUrl, \Zend\Http\Client $httpClient)
+    {
+        $this->baseUrl = $baseUrl;
+        $this->httpClient = $httpClient;
+    }
+
+    /**
+     * Fetch Links
+     *
+     * Fetches a set of links corresponding to an OpenURL
+     *
+     * @param string $openURL openURL (url-encoded)
+     *
+     * @return string         raw XML returned by resolver
+     */
+    public function fetchLinks($openURL)
+    {
+        $url = $this->baseUrl.'&'.$openURL;
+        $feed = $this->httpClient->setUri($url)->send()->getBody();
+        return $feed;
+    }
+
+
+    /**
+     * Parse Links
+     *
+     * Parses an XML file returned by a link resolver
+     * and converts it to a standardised format for display
+     *
+     * @param string $xmlstr Raw XML returned by resolver
+     *
+     * @return array         Array of values
+     */
+    public function parseLinks($xmlstr)
+    {
+        $retval = array();
+        $xml = new DOMDocument();
+        if (!@$xml->loadHTML($xmlstr)) {
+            return $retval;
+        }
+
+        $this->parseDOI($xml, $retval);
+        $this->parseRediInfo($xml, $retval);
+        $this->parseRediOpenURLs($xml, $retval);
+
+        return $retval;
+    }
+
+    /**
+     * Parse if the Redi xml snippet contains a DOI.
+     *
+     * @param DOMDocument $xml     Loaded xml document
+     * @param array       &$retval Get back a array with title, URL and service_type
+     *
+     * @return void
+     * @access private
+     */
+    protected function parseDOI ($xml, &$retval)
+    {
+        $citation = $xml->getElementById('citation');
+        if (is_object($citation->childNodes)) {
+            foreach ($citation->childNodes as $deflist) {
+                if (is_object($deflist->childNodes)) {
+                    foreach ($deflist->childNodes as $defterm) {
+                        $tmp = [];
+                        if ($defterm->hasAttributes()) {
+                            $elem = $defterm->getAttribute('class');
+                            if ($elem == 'doi_t') {
+                                $doiText = trim($defterm->nodeValue);
+                                $tmp['title'] = $doiText;
+                            }
+                            if ($elem == 'doi_d') {
+                                $doiURL = trim($this->getRediLink($defterm));
+                                $tmp['href'] = $doiURL;
+                            }
+                        }
+                        $tmp['service_type'] = 'getDOI';
+
+                        if (!empty($tmp['title']) && !empty($tmp['href'])) {
+                            $retval[] = $tmp;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parse if the Redi xml snippet contains information about the Redi offer.
+     *
+     * @param DOMDocument $xml     Loaded xml document
+     * @param array       &$retval Return array with Redi catalogue information
+     *                             consisting of Text & Link.
+     *
+     * @return void
+     * @access protected
+     */
+    protected function parseRediInfo($xml, &$retval)
+    {
+        if ($ezb = $xml->getElementById('t_ezb')) {
+            if (is_object($ezb->childNodes)) {
+                foreach ($ezb->childNodes as $divClassT) {
+                    if (is_object($divClassT->childNodes)) {
+                        foreach ($divClassT->childNodes as $nodes) {
+                            // infotext
+
+                            if ($nodes->nodeName == 'p') {
+                                $tmp = [];
+                                $rediInfoText = trim($nodes->firstChild->nodeValue);
+                                if ($this->isRediOpenURLsWithInfo($xml)) {
+                                    $rediInfoInfo = trim($nodes->nodeValue);
+                                }
+                                $rediInfoURL = trim($this->getRediLink($nodes));
+                                if (is_object($nodes->childNodes)) {
+                                    foreach ($nodes->childNodes as $bold) {
+                                        if ($bold->nodeName == 'b') {
+                                            $rediInfoText = $rediInfoText
+                                                .' '.trim($bold->nodeValue);
+                                        }
+                                    }
+                                }
+                                $tmp['title'] = (isset($rediInfoText)?
+                                    $rediInfoText:'');
+                                $tmp['href'] = (isset($rediInfoURL)?
+                                    $rediInfoURL:'');
+                                $tmp['info'] = (isset($rediInfoInfo)?
+                                    $rediInfoInfo:'');
+                                $tmp['service_type'] = 'getHolding';
+
+                                if (!empty($tmp['title']) && !empty($tmp['href'])) {
+                                    $retval[] = $tmp;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parse if the Redi xml snippet contains Redi urls.
+     *
+     * @param DOMDocument $xml     Loaded xml document
+     * @param array       &$retval Get back Redi direct link to sources
+     *                             containing title, URL and service_type
+     *
+     * @return void
+     * @access protected
+     */
+    protected function parseRediOpenURLs($xml, &$retval)
+    {
+        if ($ezb = $xml->getElementById('t_ezb')) {
+            if (is_object($ezb->childNodes)) {
+                foreach ($ezb->childNodes as $divClassT) {
+                    if (is_object($divClassT->childNodes)) {
+                        foreach ($divClassT->childNodes as $nodes) {
+                            $tmp = [];
+                            // fulltext
+                            if ($nodes->nodeName == 'div') {
+                                $text = trim(
+                                    str_replace(
+                                        array('»',
+                                            chr(194).chr(160)
+                                        ),
+                                        array('', ''),
+                                        $nodes->nodeValue
+                                    )
+                                ); // hack to replace \u00a0
+                                $available = $nodes->getElementsByTagName('span');
+                                foreach ($available as $span) {
+                                    if ($span->hasAttributes()) {
+                                        $class = $span->getAttribute('class');
+                                        if ($class == 't_link') {
+                                            $url = $this->getRediLink($nodes);
+                                        }
+                                    }
+                                }
+                                $tmp['title'] = (isset($text)?$text:'');
+                                $tmp['href'] = (isset($url)?$url:'');
+                                $tmp['service_type'] = 'getFullTxt';
+
+                                if (!empty($tmp['title']) && !empty($tmp['href'])) {
+                                    $retval[] = $tmp;
+                                }
+                            }
+                        } // end foreach
+                    } // end if
+                } // end foreach
+            } // end if
+        } // end if
+    }
+
+    /**
+     * Is a star in ReDi links text snippet
+     *
+     * @param DOMDocument $xml loaded xml document
+     *
+     * @return bool
+     * @access protected
+     */
+    protected function isRediOpenURLsWithInfo($xml)
+    {
+        if ($ezb = $xml->getElementById('t_ezb')) {
+            if (is_object($ezb->childNodes)) {
+                foreach ($ezb->childNodes as $divClassT) {
+                    if (is_object($divClassT->childNodes)) {
+                        foreach ($divClassT->childNodes as $nodes) {
+                            if ($nodes->nodeName == 'div') {
+                                $text = trim(
+                                    str_replace(
+                                        array('»',
+                                            chr(194).chr(160)
+                                        ),
+                                        array('',''),
+                                        $nodes->nodeValue
+                                    )
+                                ); // hack to replace \u00a0
+                                if (preg_match('/.*(\*).*/', $text)) {
+                                    return true;
+                                }
+                            }
+                        } // end foreach
+                    } // end if
+                } // end foreach
+            } // end if
+        } // end if
+        return false;
+    }
+
+    /**
+     * Get the ReDi links of a DOM document snippet.
+     *
+     * @param object $doc Document snippet from ReDi site
+     *
+     * @return string Url of ReDi
+     * @access protected
+     */
+    protected function getRediLink($doc)
+    {
+        $hrefs = $doc->getElementsByTagName('a');
+        foreach ($hrefs as $a) {
+            if ($a->hasAttributes()) {
+                return $a->getAttribute('href');
+            }
+        }
+        return '';
+    }
+}
\ No newline at end of file
diff --git a/module/finc/src/finc/View/Helper/Root/Factory.php b/module/finc/src/finc/View/Helper/Root/Factory.php
new file mode 100644
index 00000000000..e0ad5a2c01b
--- /dev/null
+++ b/module/finc/src/finc/View/Helper/Root/Factory.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Factory for Root view helpers.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2014.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  View_Helpers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+namespace finc\View\Helper\Root;
+use Zend\ServiceManager\ServiceManager;
+
+/**
+ * Factory for Root view helpers.
+ *
+ * @category VuFind2
+ * @package  View_Helpers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+class Factory
+{
+
+    /**
+     * Construct the Record helper.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return Record
+     */
+    public static function getRecord(ServiceManager $sm)
+    {
+        return new Record(
+            $sm->getServiceLocator()->get('VuFind\Config')->get('config')
+        );
+    }
+}
diff --git a/module/finc/src/finc/View/Helper/Root/Record.php b/module/finc/src/finc/View/Helper/Root/Record.php
new file mode 100644
index 00000000000..9f5535a086f
--- /dev/null
+++ b/module/finc/src/finc/View/Helper/Root/Record.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Record driver view helper
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  View_Helpers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+namespace finc\View\Helper\Root;
+use Zend\View\Exception\RuntimeException, Zend\View\Helper\AbstractHelper;
+
+/**
+ * Record driver view helper
+ *
+ * @category VuFind2
+ * @package  View_Helpers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+class Record extends \VuFind\View\Helper\Root\Record
+{
+    /**
+     * VuFind configuration
+     *
+     * @var \Zend\Config\Config
+     */
+    protected $config;
+
+    /**
+     * Constructor
+     *
+     * @param \Zend\Config\Config $config VuFind configuration
+     */
+    public function __construct($config = null)
+    {
+        parent::__construct($config);
+    }
+
+    /**
+     * Render the link of the type ISN.
+     *
+     * @param array $issns    Array with ISSNS
+     *
+     * @return string
+     */
+    public function getLinkISN($issns)
+    {
+        return $this->renderTemplate(
+            'link-isn.phtml', array('issns' => $issns)
+        );
+    }
+}
\ No newline at end of file
diff --git a/themes/finc/js/openurl.js b/themes/finc/js/openurl.js
new file mode 100644
index 00000000000..3a11b23995b
--- /dev/null
+++ b/themes/finc/js/openurl.js
@@ -0,0 +1,41 @@
+/*global extractClassParams, path*/
+
+function loadResolverLinks(target, openUrl) {
+    target.addClass('ajax_availability');
+    var url = path + '/AJAX/JSON?' + $.param({method:'getResolverLinks',openurl:openUrl});
+    $.ajax({
+        dataType: 'json',
+        url: url,
+        success: function(response) {
+            if (response.status == 'OK') {
+                target.removeClass('ajax_availability')
+                    .empty().append(response.data);
+            } else {
+                target.removeClass('ajax_availability').addClass('error')
+                    .empty().append(response.data);
+            }
+        }
+    });
+}
+
+var redi = {
+    init: function(doc){
+        var params = extractClassParams(doc);
+        var openUrl = $(doc).children('span.openUrl:first').attr('title');
+        $(doc).hide();
+        loadResolverLinks($('#openUrlEmbed'+params.openurl_id).removeClass('hidden'), openUrl);
+        return false;
+    }
+}
+
+$(document).ready(function() {
+    // assign action to the openUrlWindow link class
+    $('a.openUrlWindow').click(function(){
+        var params = extractClassParams(this);
+        var settings = params.window_settings;
+        window.open($(this).attr('href'), 'openurl', settings);
+        return false;
+    });
+
+    redi.init($('a.openUrlEmbed'));
+});
\ No newline at end of file
diff --git a/themes/finc/templates/RecordDriver/SolrAI/core.phtml b/themes/finc/templates/RecordDriver/SolrAI/core.phtml
new file mode 100644
index 00000000000..fc36192f92a
--- /dev/null
+++ b/themes/finc/templates/RecordDriver/SolrAI/core.phtml
@@ -0,0 +1,264 @@
+<div class="row" vocab="http://schema.org/" resource="#record" typeof="<?=$this->driver->getSchemaOrgFormats()?> Product">
+  <div class="col-sm-3">
+    <div class="text-center">
+      <? /* Display thumbnail if appropriate: */ ?>
+      <? $mediumThumb = $this->record($this->driver)->getThumbnail('medium'); $largeThumb = $this->record($this->driver)->getThumbnail('large'); ?>
+      <? if ($mediumThumb): ?>
+        <? if ($largeThumb): ?><a href="<?=$this->escapeHtmlAttr($largeThumb)?>"><? endif; ?>
+          <img alt="<?=$this->transEsc('Cover Image')?>" class="recordcover" src="<?=$this->escapeHtmlAttr($mediumThumb);?>"/>
+        <? if ($largeThumb): ?></a><? endif; ?>
+      <? else: ?>
+        <img src="<?=$this->url('cover-unavailable')?>" class="recordcover" alt="<?=$this->transEsc('No Cover Image')?>"/>
+      <? endif; ?>
+
+      <? /* Display qrcode if appropriate: */ ?>
+      <? $QRCode = $this->record($this->driver)->getQRCode("core"); ?>
+      <? if($QRCode): ?>
+        <span class="hidden-xs">
+          <br/><img alt="<?=$this->transEsc('QR Code')?>" class="qrcode" src="<?=$this->escapeHtmlAttr($QRCode);?>"/>
+        </span>
+      <? endif; ?>
+    </div>
+
+    <?=$this->record($this->driver)->getPreviews()?>
+  </div>
+
+  <div class="col-sm-9">
+    <h3 property="name"><?=$this->escapeHtml($this->driver->getTitle())?></h3>
+
+    <? $summary = $this->driver->getSummary(); $summary = isset($summary[0]) ? $summary[0] : false; ?>
+    <? if ($summary): ?>
+      <p><?=$this->truncate($summary, 300)?></p>
+
+      <? if(strlen($summary) > 300): ?>
+        <p><a href='<?=$this->recordLink()->getTabUrl($this->driver, 'Description')?>#tabnav'><?=$this->transEsc('Full description')?></a></p>
+      <? endif; ?>
+    <? endif; ?>
+
+    <? if ($this->userlist()->getMode() !== 'disabled'): ?>
+      <? /* Display the lists that this record is saved to */ ?>
+      <div class="savedLists hidden alert alert-info" id="savedLists">
+        <strong><?=$this->transEsc("Saved in")?>:</strong>
+      </div>
+    <? endif; ?>
+
+    <?/* Display Main Details */?>
+    <table class="table table-striped" summary="<?=$this->transEsc('Bibliographic Details')?>">
+      <? $journalTitle = $this->driver->getContainerTitle(); if (!empty($journalTitle)): ?>
+      <tr>
+        <th><?=$this->transEsc('Journal Title')?>:</th>
+        <td>
+          <a href="<?=$this->record($this->driver)->getLink('journaltitle', $journalTitle)?>"><?=$this->escapeHtml($journalTitle)?></a>
+          <? $ref = $this->driver->getContainerReference(); if (!empty($ref)) { echo $this->escapeHtml($ref); } ?>
+        </td>
+      </tr>
+      <? endif; ?>
+
+      <? $nextTitles = $this->driver->getNewerTitles(); $prevTitles = $this->driver->getPreviousTitles(); ?>
+      <? if (!empty($nextTitles)): ?>
+      <tr>
+        <th><?=$this->transEsc('New Title')?>: </th>
+        <td>
+          <? foreach($nextTitles as $field): ?>
+            <a href="<?=$this->record($this->driver)->getLink('title', $field)?>"><?=$this->escapeHtml($field)?></a><br/>
+          <? endforeach; ?>
+        </td>
+      </tr>
+      <? endif; ?>
+
+      <? if (!empty($prevTitles)): ?>
+      <tr>
+        <th><?=$this->transEsc('Previous Title')?>: </th>
+        <td>
+          <? foreach($prevTitles as $field): ?>
+            <a href="<?=$this->record($this->driver)->getLink('title', $field)?>"><?=$this->escapeHtml($field)?></a><br/>
+          <? endforeach; ?>
+        </td>
+      </tr>
+      <? endif; ?>
+
+      <? $authors = $this->driver->getDeduplicatedAuthors(); ?>
+      <? if (isset($authors['main']) && !empty($authors['main'])): ?>
+      <tr>
+        <th><?=$this->transEsc('Main Author')?>: </th>
+        <td property="author"><a href="<?=$this->record($this->driver)->getLink('author', $authors['main'])?>"><?=$this->escapeHtml($authors['main'])?></a></td>
+      </tr>
+      <? endif; ?>
+
+      <? if (isset($authors['corporate']) && !empty($authors['corporate'])): ?>
+      <tr>
+        <th><?=$this->transEsc('Corporate Author')?>: </th>
+        <td property="creator"><a href="<?=$this->record($this->driver)->getLink('author', $authors['corporate'])?>"><?=$this->escapeHtml($authors['corporate'])?></a></td>
+      </tr>
+      <? endif; ?>
+
+      <? if (isset($authors['secondary']) && !empty($authors['secondary'])): ?>
+      <tr>
+        <th><?=$this->transEsc('Other Authors')?>: </th>
+        <td>
+          <? $i = 0; foreach ($authors['secondary'] as $field): ?><?=($i++ == 0)?'':', '?><span property="contributor"><a href="<?=$this->record($this->driver)->getLink('author', $field)?>"><?=$this->escapeHtml($field)?></a></span><? endforeach; ?>
+        </td>
+      </tr>
+      <? endif; ?>
+
+      <? $aidatain = $this->driver->getAIDataIn(); if (!empty($aidatain)): ?>
+        <tr>
+          <th><?=$this->transEsc('In')?>: </th>
+            <td>
+              <? $issns = $aidatain['issns']; if (!empty($issns)): ?>
+                <a href="<?=$this->record($this->driver)->getLinkISN($issns)?>">
+                  <? $jtitle = $aidatain['jtitle'];
+                  if (!empty($jtitle)): ?><?=$this->escapeHtml($jtitle)?><? endif; ?>
+                </a><? endif; ?><? $volume = $aidatain['volume']; if (!empty($volume)): ?>, <?=$this->escapeHtml($volume) ?><? endif; ?><? $date = $aidatain['date']; if (!empty($date)): ?><? if (empty($volume)): ?>, <? endif; ?>(<?=$this->escapeHtml($date) ?>)<? endif; ?><? $issue = $aidatain['issue']; if (!empty($issue)): ?>, <?=$this->escapeHtml($issue) ?><? endif; ?><? $pages = $aidatain['pages']; if (!empty($pages)): ?>, <?=$this->transEsc('p.')?> <?=$this->escapeHtml($pages) ?><? endif; ?>
+              </td>
+          </tr>
+      <? endif; ?>
+
+      <? $publications = $this->driver->getPublicationDetails(); if (!empty($publications)): ?>
+        <tr>
+              <th><?=$this->transEsc('Published')?>: </th>
+              <td>
+                  <? foreach ($publications as $field): ?>
+                      <span property="publisher" typeof="Organization">
+                        <? $pubPlace = $field->getPlace(); if (!empty($pubPlace)): ?>
+                            <span property="location"><?=$this->escapeHtml($pubPlace)?></span>
+                        <? endif; ?>
+                          <? $pubName = $field->getName(); if (!empty($pubName)): ?>
+                              <span property="name"><?=$this->escapeHtml($pubName)?></span>
+                          <? endif; ?>
+                      </span>
+                      <? $pubDate = $field->getDate(); if (!empty($pubDate)): ?>
+                          <span property="publicationDate"><?=$this->escapeHtml($pubDate)?></span>
+                      <? endif; ?>
+                      <br/>
+                  <? endforeach; ?>
+              </td>
+          </tr>
+      <? endif; ?>
+
+      <? $formats = $this->driver->getFormats(); if (!empty($formats)): ?>
+        <tr>
+          <th><?=$this->transEsc('Format')?>: </th>
+          <td><?=$this->record($this->driver)->getFormatList()?></td>
+        </tr>
+      <? endif; ?>
+
+      <? $langs = $this->driver->getLanguages(); if (!empty($langs)): ?>
+        <tr>
+          <th><?=$this->transEsc('Language')?>: </th>
+          <td><? foreach ($langs as $lang): ?><?= $this->escapeHtml($lang)?><br/><? endforeach; ?></td>
+        </tr>
+      <? endif; ?>
+
+      <? $edition = $this->driver->getEdition(); if (!empty($edition)): ?>
+      <tr>
+        <th><?=$this->transEsc('Edition')?>: </th>
+        <td property="bookEdition"><?=$this->escapeHtml($edition)?></td>
+      </tr>
+      <? endif; ?>
+
+      <?/* Display series section if at least one series exists. */?>
+      <? $series = $this->driver->getSeries(); if (!empty($series)): ?>
+      <tr>
+        <th><?=$this->transEsc('Series')?>: </th>
+        <td>
+          <? foreach ($series as $field): ?>
+            <?/* Depending on the record driver, $field may either be an array with
+               "name" and "number" keys or a flat string containing only the series
+               name.  We should account for both cases to maximize compatibility. */?>
+            <? if (is_array($field)): ?>
+              <? if (!empty($field['name'])): ?>
+                <a href="<?=$this->record($this->driver)->getLink('series', $field['name'])?>"><?=$this->escapeHtml($field['name'])?></a>
+                <? if (!empty($field['number'])): ?>
+                  <?=$this->escapeHtml($field['number'])?>
+                <? endif; ?>
+                <br/>
+              <? endif; ?>
+            <? else: ?>
+              <a href="<?=$this->record($this->driver)->getLink('series', $field)?>"><?=$this->escapeHtml($field)?></a><br/>
+            <? endif; ?>
+          <? endforeach; ?>
+        </td>
+      </tr>
+      <? endif; ?>
+
+      <? $subjects = $this->driver->getAllSubjectHeadings(); if (!empty($subjects)): ?>
+      <tr>
+        <th><?=$this->transEsc('Subjects')?>: </th>
+        <td>
+          <? foreach ($subjects as $field): ?>
+          <div class="subjectLine" property="keywords">
+            <? $subject = ''; ?>
+            <? if(count($field) == 1) $field = explode('--', $field[0]); ?>
+            <? $i = 0; foreach ($field as $subfield): ?>
+              <?=($i++ == 0) ? '' : ' &gt; '?>
+              <? $subject = trim($subject . ' ' . $subfield); ?>
+              <a class="backlink" title="<?=$this->escapeHtmlAttr($subject)?>" href="<?=$this->record($this->driver)->getLink('subject', $subject)?>"><?=trim($this->escapeHtml($subfield))?></a>
+            <? endforeach; ?>
+          </div>
+          <? endforeach; ?>
+        </td>
+      </tr>
+      <? endif; ?>
+
+      <?/*
+        $openUrl = $this->driver->openURLActive('record') ? $this->driver->getOpenURL() : false;
+        // Account for replace_other_urls setting
+        $urls = ($openUrl && $this->driver->replaceURLsWithOpenURL()) ? array() : $this->record($this->driver)->getLinkDetails();
+      ?>
+      <? if (!empty($urls) || $openUrl): ?>
+      <tr>
+        <th><?=$this->transEsc('Online Access')?>: </th>
+        <td>
+          <? foreach ($urls as $current): ?>
+            <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>"><?=$this->escapeHtml($current['desc'])?></a><br/>
+          <? endforeach; ?>
+          <? if ($openUrl): ?>
+            <?=$this->openUrl($openUrl)?><br/>
+          <? endif; ?>
+        </td>
+      </tr>
+      <? endif; */?>
+
+      <? $recordLinks = $this->driver->getAllRecordLinks(); ?>
+      <? if(!empty($recordLinks)): ?>
+        <tr>
+          <th><?=$this->transEsc('Related Items')?>:</th>
+          <td>
+            <? foreach ($recordLinks as $recordLink): ?>
+              <?=$this->transEsc($recordLink['title'])?>:
+              <a href="<?=$this->recordLink()->related($recordLink['link'])?>"><?=$this->escapeHtml($recordLink['value'])?></a><br />
+            <? endforeach; ?>
+            <? /* if we have record links, display relevant explanatory notes */
+              $related = $this->driver->getRelationshipNotes();
+              if (!empty($related)): ?>
+                <? foreach ($related as $field): ?>
+                  <?=$this->escapeHtml($field)?><br/>
+                <? endforeach; ?>
+            <? endif; ?>
+          </td>
+        </tr>
+      <? endif; ?>
+
+      <? if ($this->usertags()->getMode() !== 'disabled'): ?>
+        <? $tagList = $this->driver->getTags(); ?>
+        <tr>
+          <th><?=$this->transEsc('Tags')?>: </th>
+          <td>
+            <span class="pull-right">
+              <i class="fa fa-plus"></i> <a id="tagRecord" class="modal-link" href="<?=$this->recordLink()->getActionUrl($this->driver, 'AddTag')?>" title="<?=$this->transEsc('Add Tag')?>"><?=$this->transEsc('Add Tag')?></a>
+            </span>
+            <div id="tagList">
+              <? if (count($tagList) > 0): ?>
+                <? $i = 0; foreach ($tagList as $tag): ?><?=($i++ == 0)?'':', '?><a href="<?=$this->url('tag-home')?>?lookfor=<?=urlencode($tag->tag)?>"><?=$this->escapeHtml($tag->tag)?></a> (<?=$this->escapeHtml($tag->cnt)?>)<? endforeach; ?>
+              <? else: ?>
+                <?=$this->transEsc('No Tags')?>, <?=$this->transEsc('Be the first to tag this record')?>!
+              <? endif; ?>
+            </div>
+          </td>
+        </tr>
+      <? endif; ?>
+    </table>
+    <?/* End Main Details */?>
+  </div>
+</div>
diff --git a/themes/finc/templates/RecordDriver/SolrAI/link-isn.phtml b/themes/finc/templates/RecordDriver/SolrAI/link-isn.phtml
new file mode 100644
index 00000000000..80531d8f5f9
--- /dev/null
+++ b/themes/finc/templates/RecordDriver/SolrAI/link-isn.phtml
@@ -0,0 +1 @@
+<?=$this->url('search-results')?>?join=AND&amp;bool0[]=AND&amp;<? $issns = $this->issns; if (isset($issns)): ?><? foreach ($issns as $issn): ?>lookfor0[]=<?=$this->escapeHtml($issn)?>&amp;type0[]=ISN&amp;<? endforeach; ?><? endif; ?>sort=year&amp;view=list
diff --git a/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml
new file mode 100644
index 00000000000..925e7774845
--- /dev/null
+++ b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml
@@ -0,0 +1,193 @@
+<div class="<?=$this->driver->supportsAjaxStatus()?'ajaxItem ':''?>col-xs-11">
+  <div class="row">
+    <div class="col-sm-2 col-xs-3 left">
+      <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getUniqueID())?>" class="hiddenId" />
+      <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getResourceSource())?>" class="hiddenSource" />
+      <a href="<?=$this->recordLink()->getUrl($this->driver)?>">
+        <? if ($summThumb = $this->record($this->driver)->getThumbnail()): ?>
+          <img class="recordcover" src="<?=$this->escapeHtmlAttr($summThumb)?>" alt="<?=$this->transEsc('Cover Image')?>"/>
+        <? else: ?>
+          <img class="recordcover" src="<?=$this->url('cover-unavailable')?>" alt="<?=$this->transEsc('No Cover Image')?>"/>
+        <? endif; ?>
+      </a>
+    </div>
+    <div class="col-sm-7 col-xs-6 middle">
+      <div>
+        <a href="<?=$this->recordLink()->getUrl($this->driver)?>" class="title">
+        <?
+          $summHighlightedTitle = $this->driver->getHighlightedTitle();
+          $summTitle = $this->driver->getTitle();
+          if (!empty($summHighlightedTitle)) {
+            echo $this->highlight($this->addEllipsis($summHighlightedTitle, $summTitle));
+          } else if (!empty($summTitle)) {
+            echo $this->escapeHtml($this->truncate($summTitle, 180));
+          } else {
+            echo $this->transEsc('Title not available');
+          }
+        ?>
+        </a>
+      </div>
+
+      <div>
+        <? if($this->driver->isCollection()): ?>
+          <?=implode('<br>', $this->driver->getSummary()); ?>
+        <? else: ?>
+          <? $summAuthors = $this->driver->getCombinedAuthors(); if (!empty($summAuthors)): ?>
+          <? foreach($summAuthors as $summAuthor) : ?>
+              <a href="<?=$this->record($this->driver)->getLink('author', $summAuthor)?>"><?
+                $summHighlightedAuthor = $this->driver->getHighlightedAuthor();
+                echo !empty($summHighlightedAuthor)
+                    ? $this->highlight($summHighlightedAuthor)
+                    : $this->escapeHtml($summAuthor);
+              ?></a>
+          <? endforeach; ?>
+          <? endif; ?>
+
+          <? $journalTitle = $this->driver->getContainerTitle(); $summDate = $this->driver->getPublicationDates(); $placesOfPublication = $this->driver->getPlacesOfPublication(); ?>
+          <? if (!empty($journalTitle)): ?>
+            <?=!empty($summAuthor) ? '<br />' : ''?>
+            <?=/* TODO: handle highlighting more elegantly here */ $this->transEsc('Published in') . ' <a href="' . $this->record($this->driver)->getLink('journaltitle', str_replace(array('{{{{START_HILITE}}}}', '{{{{END_HILITE}}}}'), '', $journalTitle)) . '">' . $this->highlight($journalTitle) . '</a>';?>
+            <?=!empty($summDate) ? ' (' . $this->escapeHtml($summDate) . ')' : ''?>
+          <? elseif (!empty($summDate)): ?>
+            <?=!empty($summAuthor) ? '<br />' : ''?>
+            <?=$this->transEsc('Published') . ' ' . $this->escapeHtml($summDate)?>
+          <? endif; ?>
+          <? $summInCollection = $this->driver->getContainingCollections(); if (!empty($summInCollection)): ?>
+            <? foreach ($summInCollection as $collId => $collText): ?>
+              <div>
+                <b><?=$this->transEsc("in_collection_label")?></b>
+                <a class="collectionLinkText" href="<?=$this->url('collection', array('id' => $collId))?>?recordID=<?=urlencode($this->driver->getUniqueID())?>">
+                  <?=$this->escapeHtml($collText)?>
+                </a>
+              </div>
+            <? endforeach; ?>
+          <? endif; ?>
+        <? endif; ?>
+      </div>
+
+      <? if(!$this->driver->isCollection()): ?>
+        <? if ($snippet = $this->driver->getHighlightedSnippet()): ?>
+          <? if (!empty($snippet['caption'])): ?>
+            <strong><?=$this->transEsc($snippet['caption']) ?>:</strong> ';
+          <? endif; ?>
+          <? if (!empty($snippet['snippet'])): ?>
+            <span class="quotestart">&#8220;</span>...<?=$this->highlight($snippet['snippet']) ?>...<span class="quoteend">&#8221;</span><br/>
+          <? endif; ?>
+        <? endif; ?>
+      <? endif; ?>
+
+      <?
+      /* Display information on duplicate records if available */
+      $dedupData = $this->driver->getDedupData();
+      if ($dedupData): ?>
+      <div class="dedupInformation">
+      <?
+        $i = 0;
+        foreach ($dedupData as $source => $current) {
+          if (++$i == 1) {
+            ?><span class="currentSource"><a href="<?=$this->recordLink()->getUrl($this->driver)?>"><?=$this->transEsc("source_$source", array(), $source)?></a></span><?
+          } else {
+            if ($i == 2) {
+              ?> <span class="otherSources">(<?=$this->transEsc('Other Sources')?>: <?
+            } else {
+              ?>, <?
+            }
+            ?><a href="<?=$this->recordLink()->getUrl($current['id'])?>"><?=$this->transEsc("source_$source", array(), $source)?></a><?
+          }
+        }
+        if ($i > 1) {
+          ?>)</span><?
+        }?>
+      </div>
+      <? endif; ?>
+
+      <div class="callnumAndLocation ajax-availability hidden">
+        <? if ($this->driver->supportsAjaxStatus()): ?>
+          <strong class="hideIfDetailed"><?=$this->transEsc('Call Number')?>:</strong>
+          <span class="callnumber ajax-availability hidden">
+            <?=$this->transEsc('Loading')?>...<br/>
+          </span>
+          <strong><?=$this->transEsc('Located')?>:</strong>
+          <span class="location ajax-availability hidden">
+            <?=$this->transEsc('Loading')?>...
+          </span>
+          <div class="locationDetails"></div>
+        <? else: ?>
+          <? $summCallNo = $this->driver->getCallNumber(); if (!empty($summCallNo)): ?>
+            <strong><?=$this->transEsc('Call Number')?>:</strong> <?=$this->escapeHtml($summCallNo)?>
+          <? endif; ?>
+        <? endif; ?>
+      </div>
+
+      <? /* We need to find out if we're supposed to display an OpenURL link ($openUrlActive),
+            but even if we don't plan to display the link, we still want to get the $openUrl
+            value for use in generating a COinS (Z3988) tag -- see bottom of file.
+          */
+        $openUrl = $this->driver->getOpenURL();
+        $openUrlActive = $this->driver->openURLActive('results');
+        $urls = $this->record($this->driver)->getLinkDetails();
+        if ($openUrlActive || !empty($urls)): ?>
+        <? if ($openUrlActive): ?>
+          <br/>
+          <?=$this->openUrl($openUrl)?>
+          <? if ($this->driver->replaceURLsWithOpenURL()) $urls = array(); // clear URL list if replace setting is active ?>
+        <? endif; ?>
+        <? if (!is_array($urls)) $urls = array();
+          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"></i> <?=($current['url'] == $current['desc']) ? $this->transEsc('Get full text') : $this->escapeHtml($current['desc'])?></a><br/>
+          <? endforeach; ?>
+        <? endif; ?>
+      <? endif; ?>
+
+      <?=str_replace('class="', 'class="label label-info ', $this->record($this->driver)->getFormatList())?>
+
+      <? if (!$openUrlActive && empty($urls) && $this->driver->supportsAjaxStatus()): ?>
+        <span class="status ajax-availability hidden">
+          <span class="label label-default"><?=$this->transEsc('Loading')?>...</span>
+        </span>
+      <? endif; ?>
+      <?=$this->record($this->driver)->getPreviews()?>
+    </div>
+    <div class="col-xs-3 right hidden-print">
+      <? /* Display qrcode if appropriate: */ ?>
+      <? if ($QRCode = $this->record($this->driver)->getQRCode("results")): ?>
+        <?
+          // Add JS Variables for QrCode
+          $this->jsTranslations()->addStrings(array('qrcode_hide' => 'qrcode_hide', 'qrcode_show' => 'qrcode_show'));
+        ?>
+        <span class="hidden-xs">
+          <i class="fa fa-qrcode"></i> <a href="<?=$this->escapeHtmlAttr($QRCode);?>" class="qrcodeLink"><?=$this->transEsc('qrcode_show')?></a>
+          <div class="qrcode hidden">
+            <img alt="<?=$this->transEsc('QR Code')?>" src="<?=$this->escapeHtmlAttr($QRCode);?>"/>
+          </div><br/>
+        </span>
+      <? endif; ?>
+
+      <? if ($this->userlist()->getMode() !== 'disabled'): ?>
+        <? /* Add to favorites */ ?>
+        <i class="fa fa-heart"></i> <a href="<?=$this->recordLink()->getActionUrl($this->driver, 'Save')?>" class="save-record modal-link" id="<?=$this->driver->getUniqueId() ?>" title="<?=$this->transEsc('Add to favorites')?>"><?=$this->transEsc('Add to favorites')?></a><br/>
+
+        <? /* Saved lists */ ?>
+        <div class="savedLists alert alert-info hidden">
+          <strong><?=$this->transEsc("Saved in")?>:</strong>
+        </div>
+      <? endif; ?>
+
+      <? /* Hierarchy tree link */ ?>
+      <? $trees = $this->driver->tryMethod('getHierarchyTrees'); if (!empty($trees)): ?>
+        <? foreach ($trees as $hierarchyID => $hierarchyTitle): ?>
+          <div class="hierarchyTreeLink">
+            <input type="hidden" value="<?=$this->escapeHtmlAttr($hierarchyID)?>" class="hiddenHierarchyId" />
+            <i class="fa fa-sitemap"></i>
+            <a class="hierarchyTreeLinkText modal-link" href="<?=$this->recordLink()->getTabUrl($this->driver, 'HierarchyTree')?>?hierarchy=<?=urlencode($hierarchyID)?>#tabnav" title="<?=$this->transEsc('hierarchy_tree')?>">
+              <?=$this->transEsc('hierarchy_view_context')?><? if (count($trees) > 1): ?>: <?=$this->escapeHtml($hierarchyTitle)?><? endif; ?>
+            </a>
+          </div>
+        <? endforeach; ?>
+      <? endif; ?>
+
+      <?=$openUrl?'<span class="Z3988" title="'.$this->escapeHtmlAttr($openUrl).'"></span>':''?>
+    </div>
+  </div>
+</div>
\ No newline at end of file
diff --git a/themes/finc/templates/RecordTab/description.phtml b/themes/finc/templates/RecordTab/description.phtml
new file mode 100644
index 00000000000..2d682adf1b6
--- /dev/null
+++ b/themes/finc/templates/RecordTab/description.phtml
@@ -0,0 +1,242 @@
+<?
+    // Set page title.
+    $this->headTitle($this->translate('Description') . ': ' . $this->driver->getBreadcrumb());
+
+    // Grab clean ISBN for convenience:
+    $isbn = $this->driver->getCleanISBN();
+?>
+<table class="table table-striped" summary="<?=$this->transEsc('Description')?>">
+  <? $summ = $this->driver->getSummary(); if (!empty($summ)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Summary')?>: </th>
+      <td>
+        <? foreach ($summ as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $dateSpan = $this->driver->getDateSpan(); if (!empty($dateSpan)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Published')?>: </th>
+      <td>
+        <? foreach ($dateSpan as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $notes = $this->driver->getGeneralNotes(); if (!empty($notes)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Item Description')?>: </th>
+      <td>
+        <? foreach ($notes as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $physical = $this->driver->getPhysicalDescriptions(); if (!empty($physical)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Physical Description')?>: </th>
+      <td>
+        <? foreach ($physical as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $freq = $this->driver->getPublicationFrequency(); if (!empty($freq)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Publication Frequency')?>: </th>
+      <td>
+        <? foreach ($freq as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $playTime = $this->driver->getPlayingTimes(); if (!empty($playTime)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Playing Time')?>: </th>
+      <td>
+        <? foreach ($playTime as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $system = $this->driver->getSystemDetails(); if (!empty($system)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Format')?>: </th>
+      <td>
+        <? foreach ($system as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $audience = $this->driver->getTargetAudienceNotes(); if (!empty($audience)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Audience')?>: </th>
+      <td>
+        <? foreach ($audience as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $awards = $this->driver->getAwards(); if (!empty($awards)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Awards')?>: </th>
+      <td>
+        <? foreach ($awards as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $credits = $this->driver->getProductionCredits(); if (!empty($credits)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Production Credits')?>: </th>
+      <td>
+        <? foreach ($credits as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $bib = $this->driver->getBibliographyNotes(); if (!empty($bib)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Bibliography')?>: </th>
+      <td>
+        <? foreach ($bib as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $isbns = $this->driver->getISBNs(); if (!empty($isbns)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('ISBN')?>: </th>
+      <td>
+        <? foreach ($isbns as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $issns = $this->driver->getISSNs(); if (!empty($issns)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('ISSN')?>: </th>
+      <td>
+        <? foreach ($issns as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $eissns = $this->driver->getEISSNs(); if (!empty($eissns)): ?>
+      <? $contentDisplayed = true; ?>
+      <tr>
+          <th><?=$this->transEsc('EISSN')?>: </th>
+          <td>
+              <? foreach ($eissns as $field): ?>
+                  <?=$this->escapeHtml($field)?><br/>
+              <? endforeach; ?>
+          </td>
+      </tr>
+  <? endif; ?>
+
+  <? $related = $this->driver->getRelationshipNotes(); if (!empty($related)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Related Items')?>: </th>
+      <td>
+        <? foreach ($related as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $access = $this->driver->getAccessRestrictions(); if (!empty($access)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Access')?>: </th>
+      <td>
+        <? foreach ($access as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $findingAids = $this->driver->getFindingAids(); if (!empty($findingAids)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Finding Aid')?>: </th>
+      <td>
+        <? foreach ($findingAids as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $publicationPlaces = $this->driver->getHierarchicalPlaceNames(); if (!empty($publicationPlaces)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Publication_Place')?>: </th>
+      <td>
+        <? foreach ($publicationPlaces as $field): ?>
+          <?=$this->escapeHtml($field)?><br/>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? $authorNotes = empty($isbn) ? array() : $this->authorNotes($isbn); if (!empty($authorNotes)): ?>
+    <? $contentDisplayed = true; ?>
+    <tr>
+      <th><?=$this->transEsc('Author Notes')?>: </th>
+      <td>
+        <? foreach ($authorNotes as $provider => $list): ?>
+          <? foreach ($list as $field): ?>
+            <?=$field['Content']?><br/>
+          <? endforeach; ?>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endif; ?>
+
+  <? if (!isset($contentDisplayed) || !$contentDisplayed): // Avoid errors if there were no rows above ?>
+    <tr><td><?=$this->transEsc('no_description')?></td></tr>
+  <? endif; ?>
+</table>
diff --git a/themes/finc/templates/RecordTab/staffviewarray.phtml b/themes/finc/templates/RecordTab/staffviewarray.phtml
new file mode 100644
index 00000000000..03cee1701d7
--- /dev/null
+++ b/themes/finc/templates/RecordTab/staffviewarray.phtml
@@ -0,0 +1,23 @@
+<?
+    // Set page title.
+    $this->headTitle($this->translate('Staff View') . ': ' . $this->driver->getBreadcrumb());
+?>
+<table class="citation table table-striped">
+  <? foreach ($this->driver->getRawData() as $data): ?>
+    <tr>
+      <th><?=$this->escapeHtml($data['key'])?></th>
+      <td>
+        <? if (!is_array($data['value'])) { $data['value'] = array($data['value']); } ?>
+        <? foreach ($data['value'] as $values): ?>
+          <? if (is_array($values)): ?>
+            <? foreach ($values as $value): ?>
+                <?=$this->escapeHtml($value)?><br />
+            <? endforeach; ?>
+          <? else: ?>
+            <?=$this->escapeHtml($values)?><br />
+          <? endif; ?>
+        <? endforeach; ?>
+      </td>
+    </tr>
+  <? endforeach; ?>
+</table>
\ No newline at end of file
diff --git a/themes/finc/templates/ajax/resolverLinks.phtml b/themes/finc/templates/ajax/resolverLinks.phtml
new file mode 100644
index 00000000000..7f1d204a28e
--- /dev/null
+++ b/themes/finc/templates/ajax/resolverLinks.phtml
@@ -0,0 +1,51 @@
+<div>
+  <? if (!empty($this->electronic)): ?>
+    <div class="openurls">
+      <strong><?=$this->transEsc('Electronic')?></strong>
+      <ul>
+        <? foreach ($this->electronic as $link): ?>
+          <li>
+            <? if (isset($link['href']) && !empty($link['href'])): ?>
+              <a href="<?=$this->escapeHtmlAttr($link['href'])?>" title="<?=isset($link['service_type'])?$this->escapeHtmlAttr($link['service_type']):''?>"><?=isset($link['title'])?$this->escapeHtml($link['title']):''?></a> <?=isset($link['coverage'])?$this->escapeHtml($link['coverage']):''?>
+            <? else: ?>
+              <?=isset($link['title'])?$this->escapeHtml($link['title']):''?> <?=isset($link['coverage'])?$this->escapeHtml($link['coverage']):''?>
+            <? endif; ?>
+            <? if (isset($link['info']) && !empty($link['info'])): ?>
+                <?=$this->escapeHtml($link['info'])?>
+            <? endif; ?>
+          </li>
+        <? endforeach; ?>
+      </ul>
+    </div>
+  <? endif; ?>
+  <? if (!empty($this->print)): ?>
+    <div class="openurls">
+      <strong><?=$this->transEsc('Holdings')?></strong>
+      <ul>
+        <? foreach ($this->print as $link): ?>
+          <li>
+            <? if (isset($link['href']) && !empty($link['href'])): ?>
+              <a href="<?=$this->escapeHtmlAttr($link['href'])?>" title="<?=isset($link['service_type'])?$this->escapeHtmlAttr($link['service_type']):''?>"><?=isset($link['title'])?$this->escapeHtml($link['title']):''?></a> <?=isset($link['coverage'])?$this->escapeHtml($link['coverage']):''?>
+            <? else: ?>
+              <?=isset($link['title'])?$this->escapeHtml($link['title']):''?> <?=isset($link['coverage'])?$this->escapeHtml($link['coverage']):''?>
+            <? endif; ?>
+          </li>
+        <? endforeach; ?>
+      </ul>
+    </div>
+  <? endif; ?>
+  <div class="openurls">
+    <strong><a href="<?=$this->escapeHtmlAttr($this->openUrlBase)?>?<?=$this->escapeHtmlAttr($this->openUrl)?>"><?=$this->transEsc('More options')?></a></strong>
+    <? if (!empty($this->services)): ?>
+      <ul>
+        <? foreach ($this->services as $link): ?>
+          <? if (isset($link['href']) && !empty($link['href'])): ?>
+            <li>
+              <a href="<?=$this->escapeHtmlAttr($link['href'])?>" title="<?=isset($link['service_type'])?$this->escapeHtmlAttr($link['service_type']):''?>"><?=isset($link['title'])?$this->escapeHtml($link['title']):''?></a>
+            </li>
+          <? endif; ?>
+        <? endforeach; ?>
+      </ul>
+    <? endif; ?>
+  </div>
+</div>
diff --git a/themes/finc/theme.config.php b/themes/finc/theme.config.php
index 018a473aeb6..a3a8cd17653 100644
--- a/themes/finc/theme.config.php
+++ b/themes/finc/theme.config.php
@@ -1,4 +1,9 @@
 <?php
 return array(
-    'extends' => 'bootstrap3'
+    'extends' => 'bootstrap3',
+    'helpers' => array(
+        'factories' => array(
+            'record' => 'finc\View\Helper\Root\Factory::getRecord'
+        ),
+    ),
 );
-- 
GitLab