From 3fd44facf081612724b006d93aa7220b586c199b Mon Sep 17 00:00:00 2001
From: Ere Maijala <ere.maijala@helsinki.fi>
Date: Mon, 26 Aug 2019 22:18:30 +0300
Subject: [PATCH] Add support for Alma as an OpenURL resolver. (#1410)

---
 config/vufind/config.ini                      |   4 +-
 .../src/VuFind/Resolver/Driver/Alma.php       | 160 ++++++++++
 .../VuFind/Resolver/Driver/PluginManager.php  |   2 +
 .../tests/fixtures/resolver/response/alma.xml | 301 ++++++++++++++++++
 .../VuFindTest/Resolver/Driver/AlmaTest.php   | 180 +++++++++++
 5 files changed, 645 insertions(+), 2 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Resolver/Driver/Alma.php
 create mode 100644 module/VuFind/tests/fixtures/resolver/response/alma.xml
 create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/AlmaTest.php

diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index b1d5dc0da2f..5c7c6da8066 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -1090,8 +1090,8 @@ rfr_id          = vufind.svn.sourceforge.net
 
 ; By specifying your link resolver type, you can allow VuFind to optimize its
 ; OpenURLs for a particular platform.  Current legal values: "sfx", "360link",
-; "EZB", "Redi," "demo" or "generic" (default is "generic" if commented out; "demo"
-; generates fake values for use in testing the embed setting below).
+; "EZB", "Redi", "Alma", "demo" or "generic" (default is "generic" if commented out;
+; "demo" generates fake values for use in testing the embed setting below).
 ;resolver        = sfx
 
 ; If you want OpenURL links to open in a new window, set this setting to the
diff --git a/module/VuFind/src/VuFind/Resolver/Driver/Alma.php b/module/VuFind/src/VuFind/Resolver/Driver/Alma.php
new file mode 100644
index 00000000000..c61fbf16821
--- /dev/null
+++ b/module/VuFind/src/VuFind/Resolver/Driver/Alma.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Alma Link Resolver Driver
+ *
+ * PHP version 7
+ *
+ * Copyright (C) The National Library of Finland 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Resolver_Drivers
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:link_resolver_drivers Wiki
+ */
+namespace VuFind\Resolver\Driver;
+
+/**
+ * Alma Link Resolver Driver
+ *
+ * @category VuFind
+ * @package  Resolver_Drivers
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:link_resolver_drivers Wiki
+ */
+class Alma extends AbstractBase
+{
+    /**
+     * HTTP client
+     *
+     * @var \Zend\Http\Client
+     */
+    protected $httpClient;
+
+    /**
+     * Constructor
+     *
+     * @param string            $baseUrl    Base URL for link resolver
+     * @param \Zend\Http\Client $httpClient HTTP client
+     */
+    public function __construct($baseUrl, \Zend\Http\Client $httpClient)
+    {
+        parent::__construct($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)
+    {
+        // Make the call to Alma and load results
+        $url = $this->getResolverUrl(
+            'svc_dat=CTO&response_type=xml&' . $openURL
+        );
+        return $this->httpClient->setUri($url)->send()->getBody();
+    }
+
+    /**
+     * Parse Links
+     *
+     * Parses an XML file returned by a link resolver
+     * and converts it to a standardised format for display
+     *
+     * @param string $xmlstr Raw XML returned by resolver
+     *
+     * @return array         Array of values
+     */
+    public function parseLinks($xmlstr)
+    {
+        $records = []; // array to return
+        try {
+            $xml = new \SimpleXmlElement($xmlstr);
+        } catch (\Exception $e) {
+            return $records;
+        }
+
+        foreach ($xml->context_services->children() as $service) {
+            $serviceType = $this->mapServiceType(
+                (string)$service->attributes()->service_type
+            );
+            if (!$serviceType) {
+                continue;
+            }
+            if ('getWebService' === $serviceType) {
+                $title = $this->getKeyWithId($service, 'public_name');
+                $href = $this->getKeyWithId($service, 'url');
+                $access = '';
+            } else {
+                $title = $this->getKeyWithId($service, 'package_public_name');
+                $href = (string)$service->resolution_url;
+                $access = $this->getKeyWithId($service, 'Is_free')
+                    ? 'open' : 'limited';
+            }
+            if ($coverage = $this->getKeyWithId($service, 'Availability')) {
+                $coverage = trim(str_replace('<br>', ' ', $coverage));
+            }
+
+            $record = compact('title', 'coverage', 'access', 'href');
+            $record['service_type'] = $serviceType;
+            $records[] = $record;
+        }
+        return $records;
+    }
+
+    /**
+     * Get a key with the specified id from the context_service element
+     *
+     * @param \SimpleXMLElement $service Service element
+     * @param string            $id      Key id
+     *
+     * @return string
+     */
+    protected function getKeyWithId(\SimpleXMLElement $service, $id)
+    {
+        foreach ($service->keys->children() as $key) {
+            if ((string)$key->attributes()->id === $id) {
+                return (string)$key;
+            }
+        }
+        return '';
+    }
+
+    /**
+     * Map Alma service types to VuFind. Returns an empty string for an unmapped
+     * value.
+     *
+     * @param string $serviceType Alma service type
+     *
+     * @return string
+     */
+    protected function mapServiceType($serviceType)
+    {
+        $map = [
+            'getFullTxt' => 'getFullTxt',
+            'getHolding' => 'getHolding',
+            'GeneralElectronicService' => 'getWebService'
+        ];
+        return $map[$serviceType] ?? '';
+    }
+}
diff --git a/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php b/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php
index 0c9a530c38c..a3b3ec2277d 100644
--- a/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php
+++ b/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php
@@ -47,6 +47,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
      */
     protected $aliases = [
         '360link' => Threesixtylink::class,
+        'alma' => Alma::class,
         'demo' => Demo::class,
         'ezb' => Ezb::class,
         'sfx' => Sfx::class,
@@ -62,6 +63,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
      * @var array
      */
     protected $factories = [
+        Alma::class => DriverWithHttpClientFactory::class,
         Threesixtylink::class => DriverWithHttpClientFactory::class,
         Demo::class => InvokableFactory::class,
         Ezb::class => DriverWithHttpClientFactory::class,
diff --git a/module/VuFind/tests/fixtures/resolver/response/alma.xml b/module/VuFind/tests/fixtures/resolver/response/alma.xml
new file mode 100644
index 00000000000..52db5ed50d9
--- /dev/null
+++ b/module/VuFind/tests/fixtures/resolver/response/alma.xml
@@ -0,0 +1,301 @@
+HTTP/1.1 200 OK
+Server: Apache-Coyote/1.1
+p3p: CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
+Content-Type: text/xml;charset=UTF-8
+Vary: Accept-Encoding
+Date: Mon, 19 Aug 2019 12:34:14 GMT
+
+<uresolver_content xmlns="http://com/exlibris/urm/uresolver/xmlbeans/u" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <context_object>
+    <keys>
+      <key id="rft.stitle">Fundamental Data Compression</key>
+      <key id="rft.pub">Elsevier Science</key>
+      <key id="rft.place">Burlington :</key>
+      <key id="licenseEnable">true</key>
+      <key id="sfx.sid">primo.exlibrisgroup.com</key>
+      <key id="available_services">viewit</key>
+      <key id="available_services">getit</key>
+      <key id="rft.btitle">Fundamental Data Compression</key>
+      <key id="rft.genre">book</key>
+      <key id="Incoming_URL">http%3A%2F%2Fna01.alma.exlibrisgroup.com%2Fview%2Furesolver%2FTR_INTEGRATION_INST%2Fopenurl%3Fdebug%3Dtrue%26%26u.ignore_date_coverage%3Dtrue%26rft.mms_id%3D9942811800561%26rfr_id%3Dinfo%3Asid%2Fprimo.exlibrisgroup.com%26svc_dat%3DCTO</key>
+      <key id="institution">561</key>
+      <key id="rft.eisbn_10">0-7506-6310-3</key>
+      <key id="rft.eisbn_10">0-08-053026-5</key>
+      <key id="rft.eisbn_10">1-281-03498-3</key>
+      <key id="rft.oclcnum">437189463</key>
+      <key id="http_request_type">GET</key>
+      <key id="rft.eisbn_13">978-0-7506-6310-6</key>
+      <key id="rft.eisbn_13">978-0-08-053026-0</key>
+      <key id="rft.eisbn_13">978-1-281-03498-4</key>
+      <key id="rft.normalized_eisbn">9780750663106</key>
+      <key id="req.id" xsi:nil="true"/>
+      <key id="rft.mms_id">9942811800561</key>
+      <key id="user_ip" xsi:nil="true"/>
+      <key id="inventory_id">5111975010000561</key>
+      <key id="rfr_id">info:sid/primo.exlibrisgroup.com</key>
+      <key id="rft.inventory_id">5111975010000561</key>
+      <key id="rft.eisbn">0-7506-6310-3</key>
+      <key id="rft.eisbn">0-08-053026-5</key>
+      <key id="rft.eisbn">9786611034986</key>
+      <key id="rft.eisbn">1-281-03498-3</key>
+      <key id="publication_place">Burlington :</key>
+      <key id="rft.object_type">BOOK</key>
+      <key id="rft.publisher">Elsevier Science</key>
+      <key id="rft.au">Pu, Ida Mengyi.</key>
+      <key id="ctx_id">5687947890000561</key>
+      <key id="rft.pubdate">2005.</key>
+      <key id="full_text_indicator">true</key>
+      <key id="u.ignore_date_coverage">true</key>
+      <key id="rft.title">Fundamental Data Compression</key>
+      <key id="customer">550</key>
+      <key id="rfr.rfr">primo.exlibrisgroup.com</key>
+    </keys>
+  </context_object>
+  <context_services>
+    <context_service service_type="getFullTxt" context_service_id="5687861830000561">
+      <keys>
+        <key id="package_name">Ebook Central Perpetual and DDA Titles</key>
+        <key id="package_public_name">Ebook override</key>
+        <key id="package_display_name">Ebook override</key>
+        <key id="package_internal_name">EBOOK_CENTRAL_PERPETUAL_DDA_TITLES</key>
+        <key id="interface_name">Ebook Central Perpetual and DDA Titles</key>
+        <key id="package_pid">61116046810000561</key>
+        <key id="service_type_description">Full text available via</key>
+        <key id="character_set">iso-8859-1</key>
+        <key id="Is_free">0</key>
+        <key id="portfolio_PID">53117809230000561</key>
+        <key id="cz_link_id">534330000001495973</key>
+        <key id="electronic_material_type">BOOK</key>
+        <key id="Availability">Available from 2019&lt;br&gt;</key>
+        <key id="static_url">true</key>
+        <key id="parser_program">EBOOK::Central</key>
+        <key id="parse_parameters">url=http://ebookcentral.proquest.com/lib/ &amp; cust_id=&amp;bkey=4052992</key>
+        <key id="Authentication_note"/>
+        <key id="public_note"/>
+        <key id="proxy_enabled">false</key>
+        <key id="proxy_selected">DEFAULT</key>
+        <key id="related_title">@TITLE (@RelationType)</key>
+        <key id="is_related_service">false</key>
+        <key id="is_closly_related">false</key>
+        <key id="license_exist">false</key>
+        <key id="crossref_enabled">no</key>
+        <key id="preferred_link">false</key>
+      </keys>
+      <resolution_url>https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&amp;package_service_id=5687861830000561&amp;institutionId=561&amp;customerId=550</resolution_url>
+    </context_service>
+    <context_service service_type="getFullTxt" context_service_id="5687861800000561">
+      <keys>
+        <key id="package_name">ebrary</key>
+        <key id="package_public_name">ebrary Academic Complete Subscription UKI Edition</key>
+        <key id="package_display_name">ebrary Academic Complete Subscription UKI Edition</key>
+        <key id="package_internal_name">EBRARY_ACADEMIC_COMPLETE_SUBSCRIPTION_UKI_EDITION</key>
+        <key id="interface_name">ebrary</key>
+        <key id="package_pid">6128319920000561</key>
+        <key id="service_type_description">Full text available via</key>
+        <key id="character_set">utf8</key>
+        <key id="Is_free">0</key>
+        <key id="portfolio_PID">5332048890000561</key>
+        <key id="cz_link_id">533790000000347286</key>
+        <key id="electronic_material_type">BOOK</key>
+        <key id="Availability"/>
+        <key id="static_url">true</key>
+        <key id="parser_program">EBRARY::EBOOKS</key>
+        <key id="parse_parameters">url=http://site.ebrary.com &amp;cust_id=yes&amp;bkey=10190890</key>
+        <key id="Authentication_note"/>
+        <key id="public_note"/>
+        <key id="proxy_enabled">false</key>
+        <key id="proxy_selected">DEFAULT</key>
+        <key id="related_title">@TITLE (@RelationType)</key>
+        <key id="is_related_service">false</key>
+        <key id="is_closly_related">false</key>
+        <key id="license_exist">false</key>
+        <key id="crossref_enabled">no</key>
+        <key id="preferred_link">false</key>
+      </keys>
+      <resolution_url>https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&amp;package_service_id=5687861800000561&amp;institutionId=561&amp;customerId=550</resolution_url>
+    </context_service>
+    <context_service service_type="getFullTxt" context_service_id="5687861790000561">
+      <keys>
+        <key id="package_name">ebrary</key>
+        <key id="package_public_name">ebrary Science &amp; Technology Subscription</key>
+        <key id="package_display_name">ebrary Science &amp; Technology Subscription</key>
+        <key id="package_internal_name">EBRARY_SCIENCE_AND_TECHNOLOGY_SUBSCRIPTION</key>
+        <key id="interface_name">ebrary</key>
+        <key id="package_pid">6134559200000561</key>
+        <key id="service_type_description">Full text available via</key>
+        <key id="character_set">utf8</key>
+        <key id="Is_free">0</key>
+        <key id="portfolio_PID">5334835190000561</key>
+        <key id="cz_link_id">533790000000521471</key>
+        <key id="electronic_material_type">BOOK</key>
+        <key id="Availability"/>
+        <key id="static_url">true</key>
+        <key id="parser_program">EBRARY::EBOOKS</key>
+        <key id="parse_parameters">url=http://site.ebrary.com &amp;cust_id=&amp;bkey=10190890</key>
+        <key id="Authentication_note"/>
+        <key id="public_note"/>
+        <key id="proxy_enabled">false</key>
+        <key id="proxy_selected">DEFAULT</key>
+        <key id="related_title">@TITLE (@RelationType)</key>
+        <key id="is_related_service">false</key>
+        <key id="is_closly_related">false</key>
+        <key id="license_exist">false</key>
+        <key id="crossref_enabled">no</key>
+        <key id="preferred_link">false</key>
+      </keys>
+      <resolution_url>https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&amp;package_service_id=5687861790000561&amp;institutionId=561&amp;customerId=550</resolution_url>
+    </context_service>
+    <context_service service_type="getFullTxt" context_service_id="5687861770000561">
+      <keys>
+        <key id="package_name">EBSCOhost</key>
+        <key id="package_public_name">EBSCOhost Academic eBook Collection (North America)</key>
+        <key id="package_display_name">EBSCOhost Academic eBook Collection (North America)</key>
+        <key id="package_internal_name">EBSCOHOST_EBOOKS_ACADEMIC_COLLECTION_NORTH_AMERICA</key>
+        <key id="interface_name">EBSCOhost</key>
+        <key id="package_pid">6178520000000561</key>
+        <key id="service_type_description">Full text available via</key>
+        <key id="character_set">iso-8859-1</key>
+        <key id="Is_free">1</key>
+        <key id="portfolio_PID">5379940190000561</key>
+        <key id="cz_link_id">533640000000243115</key>
+        <key id="electronic_material_type">BOOK</key>
+        <key id="Availability"/>
+        <key id="static_url">true</key>
+        <key id="parser_program">EBSCO_HOST::netlibrary</key>
+        <key id="parse_parameters">url=http://search.ebscohost.com &amp; shib= &amp; customer_id=&amp;ID=207290</key>
+        <key id="Authentication_note">collection level auth&lt;br/>SERVICE LEVEL AUTHE NOTE&lt;br/></key>
+        <key id="public_note">notessssssssssss&lt;br/>SERVICE LEVEL PUBLIC NOTE&lt;br/></key>
+        <key id="proxy_enabled">false</key>
+        <key id="proxy_selected">DEFAULT</key>
+        <key id="related_title">@TITLE (@RelationType)</key>
+        <key id="is_related_service">false</key>
+        <key id="is_closly_related">false</key>
+        <key id="license_exist">false</key>
+        <key id="crossref_enabled">no</key>
+        <key id="preferred_link">false</key>
+      </keys>
+      <resolution_url>https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&amp;package_service_id=5687861770000561&amp;institutionId=561&amp;customerId=550</resolution_url>
+    </context_service>
+    <context_service service_type="getHolding" context_service_id="5687861780000561">
+      <keys>
+        <key id="package_name">EBSCOhost</key>
+        <key id="package_public_name">EBSCOhost eBook Community College Collection</key>
+        <key id="package_display_name">EBSCOhost eBook Community College Collection</key>
+        <key id="package_internal_name">EBSCOHOST_EBOOKS_COMMUNITY_COLLEGE_COLLECTION</key>
+        <key id="interface_name">EBSCOhost</key>
+        <key id="package_pid">6124119500000561</key>
+        <key id="service_type_description">Full text available via</key>
+        <key id="character_set">utf8</key>
+        <key id="Is_free">0</key>
+        <key id="portfolio_PID">5368625580000561</key>
+        <key id="cz_link_id">532560000001468461</key>
+        <key id="electronic_material_type">BOOK</key>
+        <key id="Availability"/>
+        <key id="static_url">true</key>
+        <key id="parser_program">EBSCO_HOST::netlibrary</key>
+        <key id="parse_parameters">url=http://search.ebscohost.com &amp; shib=test34 &amp; customer_id=test34&amp;ID=207290</key>
+        <key id="Authentication_note"/>
+        <key id="public_note"/>
+        <key id="proxy_enabled">true</key>
+        <key id="proxy_selected">proxy2</key>
+        <key id="related_title">@TITLE (@RelationType)</key>
+        <key id="is_related_service">false</key>
+        <key id="is_closly_related">false</key>
+        <key id="license_exist">false</key>
+        <key id="crossref_enabled">no</key>
+        <key id="preferred_link">false</key>
+      </keys>
+      <resolution_url>https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&amp;package_service_id=5687861780000561&amp;institutionId=561&amp;customerId=550</resolution_url>
+    </context_service>
+    <context_service service_type="getFullTxt" context_service_id="5687861820000561">
+      <keys>
+        <key id="package_name">Elsevier ScienceDirect</key>
+        <key id="package_public_name">Elsevier ScienceDirect Books</key>
+        <key id="package_display_name">Elsevier ScienceDirect Books</key>
+        <key id="package_internal_name">ELSEVIER_SD_BOOKS</key>
+        <key id="interface_name">Elsevier ScienceDirect</key>
+        <key id="package_pid">6111920000000561</key>
+        <key id="service_type_description">Full text available via</key>
+        <key id="character_set">iso-8859-1</key>
+        <key id="Is_free">0</key>
+        <key id="portfolio_PID">5311970200000561</key>
+        <key id="cz_link_id">531000000000933152</key>
+        <key id="electronic_material_type">BOOK</key>
+        <key id="Availability"/>
+        <key id="static_url">true</key>
+        <key id="parser_program">ELSEVIER::SCIENCE_DIRECT_BOOKS</key>
+        <key id="parse_parameters">url=http://www.sciencedirect.com/science &amp; shib=&amp;bkey=9780750663106</key>
+        <key id="Authentication_note"/>
+        <key id="public_note"/>
+        <key id="proxy_enabled">false</key>
+        <key id="proxy_selected">DEFAULT</key>
+        <key id="related_title">@TITLE (@RelationType)</key>
+        <key id="is_related_service">false</key>
+        <key id="is_closly_related">false</key>
+        <key id="license_exist">false</key>
+        <key id="crossref_enabled">no</key>
+        <key id="preferred_link">false</key>
+      </keys>
+      <resolution_url>https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&amp;package_service_id=5687861820000561&amp;institutionId=561&amp;customerId=550</resolution_url>
+    </context_service>
+    <context_service service_type="GeneralElectronicService" context_service_id="2336397">
+      <keys>
+        <key id="code">LIBR</key>
+        <key id="name">Request Assistance for this Resource!</key>
+        <key id="public_name">Request Assistance for this Resource!</key>
+        <key id="url">https://www.google.com/search?Testingrft.oclcnum=437189463&amp;q=Fundamental+Data+Compression&amp;rft.archive=9942811800561</key>
+        <key id="service_order">0</key>
+      </keys>
+    </context_service>
+    <context_service service_type="getFullTxt" context_service_id="5687861810000561">
+      <keys>
+        <key id="package_name">Proquest</key>
+        <key id="package_public_name">ProQuest Safari Tech Books Online</key>
+        <key id="package_display_name">ProQuest Safari Tech Books Online</key>
+        <key id="package_internal_name">PROQUEST_SAFARI_TECH_BOOKS_ONLINE</key>
+        <key id="interface_name">Proquest</key>
+        <key id="package_pid">6112970000000561</key>
+        <key id="service_type_description">Full text available via</key>
+        <key id="character_set">iso-8859-1</key>
+        <key id="Is_free">0</key>
+        <key id="portfolio_PID">5326327260000561</key>
+        <key id="cz_link_id">533710000002010686</key>
+        <key id="electronic_material_type">BOOK</key>
+        <key id="Availability"/>
+        <key id="static_url">true</key>
+        <key id="parser_program">PROQUEST::SAFARI</key>
+        <key id="parse_parameters">url=http://proquestcombo.safaribooksonline.com &amp; uicode= &amp;shib= &amp;shire=https://authenticate.bvdep.com/ukfederation/Shibboleth.sso/SAML/POST &amp;provider=https://authenticate.bvdep.com/ukfederation&amp;bkey=9780750663106</key>
+        <key id="Authentication_note"/>
+        <key id="public_note"/>
+        <key id="proxy_enabled">true</key>
+        <key id="proxy_selected">Proxy 1</key>
+        <key id="related_title">@TITLE (@RelationType)</key>
+        <key id="is_related_service">false</key>
+        <key id="is_closly_related">false</key>
+        <key id="license_exist">false</key>
+        <key id="crossref_enabled">no</key>
+        <key id="Filtered">true</key>
+        <key id="Filter reason">Available for - No group for the incoming ip and AF line for the portfolio/service/package exists</key>
+        <key id="preferred_link">false</key>
+      </keys>
+      <resolution_url>https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&amp;package_service_id=5687861810000561&amp;institutionId=561&amp;customerId=550</resolution_url>
+    </context_service>
+  </context_services>
+  <performance_counters>
+    <performance_counter name="TOTAL" duration="0.0"/>
+    <performance_counter name="MMS_LOOKUP" duration="0.0"/>
+    <performance_counter name="ENRICH" duration="0.0"/>
+    <performance_counter name="PARSE" duration="0.0"/>
+    <performance_counter name="GET_SERVICES" duration="1.109"/>
+    <performance_counter name="FILTER" duration="0.249"/>
+    <performance_counter name="SAVE" duration="0.202"/>
+    <performance_counter name="GET_ZERO_TITLE_SERVICES" duration="0.0"/>
+    <performance_counter name="FETCH_SERVICE_THRESHOLD_RULES" duration="0.0"/>
+    <performance_counter name="CREATE_ZERO_TITLE_SERVICES" duration="0.0"/>
+    <performance_counter name="SAVE_ZERO_TITLE_SERVICES" duration="0.0"/>
+    <performance_counter name="GET_SINGLE_SERVICE" duration="0.0"/>
+    <performance_counter name="EXECUTE_TARGET_PARSER" duration="0.0"/>
+    <performance_counter name="GET_URESOLVER_CONTENT" duration="0.0"/>
+    <performance_counter name="UPDATE_SELECTED" duration="0.0"/>
+  </performance_counters>
+</uresolver_content>
\ No newline at end of file
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/AlmaTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/AlmaTest.php
new file mode 100644
index 00000000000..5f972f4a2cd
--- /dev/null
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/AlmaTest.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Alma resolver driver test
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Leipzig University Library 2015.
+ * Copyright (C) The National Library of Finland 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Tests
+ * @author   André Lahmann <lahmann@ub.uni-leipzig.de>
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Page
+ */
+namespace VuFindTest\Resolver\Driver;
+
+use InvalidArgumentException;
+
+use VuFind\Resolver\Driver\Alma;
+use Zend\Http\Client\Adapter\Test as TestAdapter;
+
+use Zend\Http\Response as HttpResponse;
+
+/**
+ * Alma resolver driver test
+ *
+ * @category VuFind
+ * @package  Tests
+ * @author   André Lahmann <lahmann@ub.uni-leipzig.de>
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Page
+ */
+class AlmaTest extends \VuFindTest\Unit\TestCase
+{
+    /**
+     * Test-Config
+     *
+     * @var array
+     */
+    protected $openUrlConfig = [
+        'OpenURL' => [
+            'url' => "http://na01.alma.exlibrisgroup.com/view/uresolver/TR_INTEGRATION_INST/openurl?debug=true&u.ignore_date_coverage=true&rft.mms_id=9942811800561&rfr_id=info:sid/primo.exlibrisgroup.com&svc_dat=CTO",
+            'rfr_id' => "vufind.svn.sourceforge.net",
+            'resolver' => "alma",
+            '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,
+            'show_in_record' => false,
+            'show_in_holdings' => true,
+            'embed' => true,
+            'replace_other_urls' => true
+        ],
+    ];
+
+    /**
+     * Test
+     *
+     * @return void
+     */
+    public function testParseLinks()
+    {
+        $conn = $this->createConnector('alma.xml');
+
+        $openUrl = "url_ver=Z39.88-2004&ctx_ver=Z39.88-2004";
+        $result = $conn->parseLinks($conn->fetchLinks($openUrl));
+
+        $testResult = [
+            0 => [
+                'title' => 'Ebook override',
+                'coverage' => 'Available from 2019',
+                'access' => 'limited',
+                'href' => 'https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&package_service_id=5687861830000561&institutionId=561&customerId=550',
+                'service_type' => 'getFullTxt',
+            ],
+            1 => [
+                'title' => 'ebrary Academic Complete Subscription UKI Edition',
+                'coverage' => '',
+                'access' => 'limited',
+                'href' => 'https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&package_service_id=5687861800000561&institutionId=561&customerId=550',
+                'service_type' => 'getFullTxt',
+            ],
+            2 => [
+                'title' => 'ebrary Science & Technology Subscription',
+                'coverage' => '',
+                'access' => 'limited',
+                'href' => 'https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&package_service_id=5687861790000561&institutionId=561&customerId=550',
+                'service_type' => 'getFullTxt',
+            ],
+            3 => [
+                'title' => 'EBSCOhost Academic eBook Collection (North America)',
+                'coverage' => '',
+                'access' => 'open',
+                'href' => 'https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&package_service_id=5687861770000561&institutionId=561&customerId=550',
+                'service_type' => 'getFullTxt',
+            ],
+            4 => [
+                'title' => 'EBSCOhost eBook Community College Collection',
+                'coverage' => '',
+                'access' => 'limited',
+                'href' => 'https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&package_service_id=5687861780000561&institutionId=561&customerId=550',
+                'service_type' => 'getHolding',
+            ],
+            5 => [
+                'title' => 'Elsevier ScienceDirect Books',
+                'coverage' => '',
+                'access' => 'limited',
+                'href' => 'https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&package_service_id=5687861820000561&institutionId=561&customerId=550',
+                'service_type' => 'getFullTxt',
+            ],
+            6 => [
+                'title' => 'Request Assistance for this Resource!',
+                'coverage' => '',
+                'access' => '',
+                'href' => 'https://www.google.com/search?Testingrft.oclcnum=437189463&q=Fundamental+Data+Compression&rft.archive=9942811800561',
+                'service_type' => 'getWebService',
+            ],
+            7 => [
+                'title' => 'ProQuest Safari Tech Books Online',
+                'coverage' => '',
+                'access' => 'limited',
+                'href' => 'https://na01.alma.exlibrisgroup.com/view/action/uresolver.do?operation=resolveService&package_service_id=5687861810000561&institutionId=561&customerId=550',
+                'service_type' => 'getFullTxt',
+            ],
+        ];
+
+        $this->assertEquals($result, $testResult);
+    }
+
+    /**
+     * Create connector with fixture file.
+     *
+     * @param string $fixture Fixture file
+     *
+     * @return Connector
+     *
+     * @throws InvalidArgumentException Fixture file does not exist
+     */
+    protected function createConnector($fixture = null)
+    {
+        $adapter = new TestAdapter();
+        if ($fixture) {
+            $file = realpath(
+                __DIR__ .
+                '/../../../../../../tests/fixtures/resolver/response/' . $fixture
+            );
+            if (!is_string($file) || !file_exists($file) || !is_readable($file)) {
+                throw new InvalidArgumentException(
+                    sprintf('Unable to load fixture file: %s ', $file)
+                );
+            }
+            $response = file_get_contents($file);
+            $responseObj = HttpResponse::fromString($response);
+            $adapter->setResponse($responseObj);
+        }
+        $_SERVER['REMOTE_ADDR'] = "127.0.0.1";
+
+        $client = new \Zend\Http\Client();
+        $client->setAdapter($adapter);
+
+        $conn = new Alma($this->openUrlConfig['OpenURL']['url'], $client);
+        return $conn;
+    }
+}
-- 
GitLab