Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.68% covered (warning)
75.68%
112 / 148
50.00% covered (danger)
50.00%
7 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Base
75.68% covered (warning)
75.68%
112 / 148
50.00% covered (danger)
50.00%
7 / 14
76.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
9.00
 info
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 createSession
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 retrieve
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 retrieveEdsItem
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 retrieveEpfItem
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 search
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 parseAutocomplete
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 autocomplete
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 authenticate
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 createQSFromArray
31.58% covered (danger)
31.58%
6 / 19
0.00% covered (danger)
0.00%
0 / 1
28.50
 call
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
3
 process
33.33% covered (danger)
33.33%
3 / 9
0.00% covered (danger)
0.00%
0 / 1
5.67
 setTokens
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 httpRequest
n/a
0 / 0
n/a
0 / 0
0
1<?php
2
3/**
4 * EBSCO Search API abstract base class
5 *
6 * PHP version 8
7 *
8 * Copyright (C) EBSCO Industries 2013
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category EBSCOIndustries
24 * @package  EBSCO
25 * @author   Michelle Milton <mmilton@epnet.com>
26 * @author   Cornelius Amzar <cornelius.amzar@bsz-bw.de>
27 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
28 * @link     http://edswiki.ebscohost.com/EDS_API_Documentation
29 */
30
31namespace VuFindSearch\Backend\EDS;
32
33use Laminas\Log\LoggerAwareInterface;
34
35use function is_array;
36
37/**
38 * EBSCO Search API abstract base class
39 *
40 * @category EBSCOIndustries
41 * @package  EBSCO
42 * @author   Michelle Milton <mmilton@epnet.com>
43 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
44 * @link     http://edswiki.ebscohost.com/EDS_API_Documentation
45 */
46abstract class Base implements LoggerAwareInterface
47{
48    use \VuFind\Log\LoggerAwareTrait;
49
50    /**
51     * EDS or EPF API host.
52     *
53     * @var string
54     */
55    protected $apiHost;
56
57    /**
58     * Auth host
59     *
60     * @var string
61     */
62    protected $authHost = 'https://eds-api.ebscohost.com/authservice/rest';
63
64    /**
65     * Session host.
66     *
67     * @var string
68     */
69    protected $sessionHost = 'https://eds-api.ebscohost.com/edsapi/rest';
70
71    /**
72     * The organization id use for authentication
73     *
74     * @var ?string
75     */
76    protected $orgId;
77
78    /**
79     * Accept header
80     *
81     * @var string
82     */
83    protected $accept  = 'application/json';
84
85    /**
86     * Content type header
87     *
88     * @var string
89     */
90    protected $contentType = 'application/json';
91
92    /**
93     * Search HTTP method
94     *
95     * @var string
96     */
97    protected $searchHttpMethod = 'POST';
98
99    /**
100     * Constructor
101     *
102     * Sets up the EDS API Client
103     *
104     * @param array $settings Associative array of setting to use in
105     *                        conjunction with the EDS API
106     *    <ul>
107     *      <li>orgid - Organization making calls to the EDS API </li>
108     *      <li>search_http_method - HTTP method for search API calls</li>
109     *    </ul>
110     */
111    public function __construct($settings = [])
112    {
113        if (is_array($settings)) {
114            foreach ($settings as $key => $value) {
115                switch ($key) {
116                    case 'api_url':
117                        $this->apiHost = $value;
118                        break;
119                    case 'auth_url':
120                        $this->authHost = $value;
121                        break;
122                    case 'session_url':
123                        $this->sessionHost = $value;
124                        break;
125                    case 'orgid':
126                        $this->orgId = $value;
127                        break;
128                    case 'search_http_method':
129                        $this->searchHttpMethod = $value;
130                }
131            }
132        }
133    }
134
135    /**
136     * Obtain edsapi search critera and application related settings
137     *
138     * @param string $authenticationToken Authentication token
139     * @param string $sessionToken        Session token
140     *
141     * @return array
142     */
143    public function info($authenticationToken = null, $sessionToken = null)
144    {
145        $this->debug('Info');
146        $url = $this->apiHost . '/info';
147        $headers = $this->setTokens($authenticationToken, $sessionToken);
148        return $this->call($url, $headers);
149    }
150
151    /**
152     * Creates a new session
153     *
154     * @param string $profile   Profile to use
155     * @param string $isGuest   Whether or not this session will be a guest session
156     * @param string $authToken Authentication token
157     *
158     * @return array
159     */
160    public function createSession(
161        $profile = null,
162        $isGuest = null,
163        $authToken = null
164    ) {
165        $this->debug(
166            'Create Session for profile: '
167            . "$profile, guest: $isGuest, authToken: $authToken "
168        );
169        $qs = ['profile' => $profile, 'guest' => $isGuest];
170        $url = $this->sessionHost . '/createsession';
171        $headers = $this->setTokens($authToken, null);
172        return $this->call($url, $headers, $qs, 'GET', null, '', false);
173    }
174
175    /**
176     * Retrieves an EDS record specified by its identifiers
177     *
178     * @param string $an                  An of the record to retrieve from the
179     * EdsApi
180     * @param string $dbId                Database identifier of the record to
181     * retrieve from the EdsApi
182     * @param string $authenticationToken Authentication token
183     * @param string $sessionToken        Session token
184     * @param string $highlightTerms      Comma separated list of terms to highlight
185     * in the retrieved record responses
186     * @param array  $extraQueryParams    Extra query string parameters
187     *
188     * @return array    The requested record
189     *
190     * @deprecated Use retrieveEdsItem
191     */
192    public function retrieve(
193        $an,
194        $dbId,
195        $authenticationToken,
196        $sessionToken,
197        $highlightTerms = null,
198        $extraQueryParams = []
199    ) {
200        return $this->retrieveEdsItem(
201            $an,
202            $dbId,
203            $authenticationToken,
204            $sessionToken,
205            $highlightTerms,
206            $extraQueryParams
207        );
208    }
209
210    /**
211     * Retrieves an EDS record specified by its identifiers
212     *
213     * @param string $an                  An of the record to retrieve from the
214     * EdsApi
215     * @param string $dbId                Database identifier of the record to
216     * retrieve from the EdsApi
217     * @param string $authenticationToken Authentication token
218     * @param string $sessionToken        Session token
219     * @param string $highlightTerms      Comma separated list of terms to highlight
220     * in the retrieved record responses
221     * @param array  $extraQueryParams    Extra query string parameters
222     *
223     * @return array    The requested record
224     */
225    public function retrieveEdsItem(
226        $an,
227        $dbId,
228        $authenticationToken,
229        $sessionToken,
230        $highlightTerms = null,
231        $extraQueryParams = []
232    ) {
233        $this->debug(
234            "Get Record. an: $an, dbid: $dbId$highlightTerms$highlightTerms"
235        );
236        $qs = $extraQueryParams + ['an' => $an, 'dbid' => $dbId];
237        if (null != $highlightTerms) {
238            $qs['highlightterms'] = $highlightTerms;
239        }
240        $url = $this->apiHost . '/retrieve';
241        $headers = $this->setTokens($authenticationToken, $sessionToken);
242        return $this->call($url, $headers, $qs);
243    }
244
245    /**
246     * Retrieves an EPF record specified by its identifiers
247     *
248     * @param string $pubId               Id of the record to retrieve from the
249     * EpfApi
250     * @param string $authenticationToken Authentication token
251     * @param string $sessionToken        Session token
252     *
253     * @return array    The requested record
254     */
255    public function retrieveEpfItem(
256        $pubId,
257        $authenticationToken,
258        $sessionToken
259    ) {
260        $this->debug(
261            "Get Record. pubId: $pubId"
262        );
263        $qs = ['id' => $pubId];
264        $url = $this->apiHost . '/retrieve';
265        $headers = $this->setTokens($authenticationToken, $sessionToken);
266        return $this->call($url, $headers, $qs);
267    }
268
269    /**
270     * Execute an EdsApi search
271     *
272     * @param SearchRequestModel $query               Search request object
273     * @param string             $authenticationToken Authentication token
274     * @param string             $sessionToken        Session token
275     *
276     * @return array An array of query results as returned from the api
277     */
278    public function search($query, $authenticationToken, $sessionToken)
279    {
280        // Query String Parameters
281        $method = $this->searchHttpMethod;
282        $json = $method === 'GET' ? null : $query->convertToSearchRequestJSON();
283        $qs = $method === 'GET' ? $query->convertToQueryStringParameterArray() : [];
284        $this->debug(
285            'Query: ' . ($method === 'GET' ? $this->varDump($qs) : $json)
286        );
287        $url = $this->apiHost . '/search';
288        $headers = $this->setTokens($authenticationToken, $sessionToken);
289        return $this->call($url, $headers, $qs, $method, $json);
290    }
291
292    /**
293     * Parse autocomplete response from API in an array of terms
294     *
295     * @param array $msg Response from API
296     *
297     * @return array of terms
298     */
299    protected function parseAutocomplete($msg)
300    {
301        $result = [];
302        if (isset($msg['terms']) && is_array($msg['terms'])) {
303            foreach ($msg['terms'] as $value) {
304                $result[] = $value['term'];
305            }
306        }
307        return $result;
308    }
309
310    /**
311     * Execute an EdsApi autocomplete
312     *
313     * @param string $query Search term
314     * @param string $type  Autocomplete type (e.g. 'rawqueries' or 'holdings')
315     * @param array  $data  Autocomplete API details (from authenticating with
316     * 'autocomplete' option set -- requires token, custid and url keys).
317     * @param bool   $raw   Should we return the results raw (true) or processed
318     * (false)?
319     *
320     * @return array An array of autocomplete terns as returned from the api
321     */
322    public function autocomplete($query, $type, $data, $raw = false)
323    {
324        // $filters is an array of filter objects
325        // filter objects consist of name and an array of values (customer ids)
326        $filters = [['name' => 'custid', 'values' => [$data['custid']]]];
327
328        $params = [
329            'idx' => $type,
330            'token' => $data['token'],
331            'filters' => json_encode($filters),
332            'term' => $query,
333        ];
334
335        $url = $data['url'] . '?' . http_build_query($params);
336
337        $this->debug('Autocomplete URL: ' . $url);
338        $response = $this->call($url, null, null, 'GET', null);
339        return $raw ? $response : $this->parseAutocomplete($response);
340    }
341
342    /**
343     * Generate an authentication token with a valid EBSCO EDS Api account
344     *
345     * @param string $username username associated with an EBSCO EdsApi account
346     * @param string $password password associated with an EBSCO EdsApi account
347     * @param string $orgid    Organization id the request is initiated from
348     * @param array  $params   optional params (autocomplete)
349     *
350     * @return array
351     */
352    public function authenticate(
353        $username = null,
354        $password = null,
355        $orgid = null,
356        $params = null
357    ) {
358        $this->debug(
359            "Authenticating: username: $username, password: XXXXXXX, orgid: $orgid"
360        );
361        $url = $this->authHost . '/uidauth';
362        $org = $orgid ?? $this->orgId;
363        $authInfo = [];
364        if (isset($username)) {
365            $authInfo['UserId'] = $username;
366        }
367        if (isset($password)) {
368            $authInfo['Password'] = $password;
369        }
370        if (isset($org)) {
371            $authInfo['orgid'] = $org;
372        }
373        if (isset($params)) {
374            $authInfo['Options'] = $params;
375        }
376        $messageBody = json_encode($authInfo);
377        return $this->call($url, null, null, 'POST', $messageBody, '', false);
378    }
379
380    /**
381     * Convert an array of search parameters to EDS API querystring parameters
382     *
383     * @param array $params Parameters to convert to querystring parameters
384     *
385     * @return array
386     */
387    protected function createQSFromArray($params)
388    {
389        $queryParameters = [];
390        if (null != $params && is_array($params)) {
391            foreach ($params as $key => $value) {
392                if (is_array($value)) {
393                    $parameterName = $key;
394                    if (SearchRequestModel::isParameterIndexed($parameterName)) {
395                        $parameterName = SearchRequestModel::getIndexedParameterName(
396                            $parameterName
397                        );
398                    }
399                    $cnt = 0;
400                    foreach ($value as $subValue) {
401                        $cnt = $cnt + 1;
402                        $finalParameterName = $parameterName;
403                        if (SearchRequestModel::isParameterIndexed($key)) {
404                            $finalParameterName = $parameterName . '-' . $cnt;
405                        }
406                        $queryParameters[]
407                            = $finalParameterName . '=' . urlencode($subValue);
408                    }
409                } else {
410                    $queryParameters[] = $key . '=' . urlencode($value ?? '');
411                }
412            }
413        }
414        return $queryParameters;
415    }
416
417    /**
418     * Submit REST Request
419     *
420     * @param string $baseUrl       URL of service
421     * @param array  $headerParams  An array of headers to add to the request
422     * @param array  $params        An array of parameters for the request
423     * @param string $method        The HTTP Method to use
424     * @param string $message       Message to POST if $method is POST
425     * @param string $messageFormat Format of request $messageBody and responses
426     * @param bool   $cacheable     Whether the request is cacheable
427     *
428     * @throws ApiException
429     * @return object         EDS API response (or an Error object).
430     */
431    protected function call(
432        $baseUrl,
433        $headerParams,
434        $params = [],
435        $method = 'GET',
436        $message = null,
437        $messageFormat = '',
438        $cacheable = true
439    ) {
440        // Build Query String Parameters
441        $queryParameters = $this->createQSFromArray($params);
442        $queryString = implode('&', $queryParameters);
443        $this->debug("Querystring to use: $queryString ");
444        // Build headers
445        $headers = [
446            'Accept' => $this->accept,
447            'Content-Type' => $this->contentType,
448            'Accept-Encoding' => 'gzip,deflate',
449        ];
450        if (null != $headerParams) {
451            foreach ($headerParams as $key => $value) {
452                $headers[$key] = $value;
453            }
454        }
455        $response = $this->httpRequest(
456            $baseUrl,
457            $method,
458            $queryString,
459            $headers,
460            $message,
461            $messageFormat,
462            $cacheable
463        );
464        return $this->process($response);
465    }
466
467    /**
468     * Process EDS API response message
469     *
470     * @param string $input The raw response from EDS API
471     *
472     * @throws ApiException
473     * @return array        The processed response from EDS API
474     */
475    protected function process($input)
476    {
477        //process response.
478        try {
479            $result = json_decode($input, true);
480        } catch (\Exception $e) {
481            throw new ApiException(
482                'An error occurred when processing EDS Api response: '
483                . $e->getMessage()
484            );
485        }
486        if (!isset($result)) {
487            throw new ApiException('Unknown error processing response');
488        }
489        return $result;
490    }
491
492    /**
493     * Populate an associative array of session and authentication parameters to
494     * send to the EDS API
495     *
496     * @param string $authenticationToken Authentication token to add
497     * @param string $sessionToken        Session token to add
498     *
499     * @return array Associative array of header parameters to add.
500     */
501    protected function setTokens($authenticationToken = null, $sessionToken = null)
502    {
503        $headers = [];
504        if (!empty($authenticationToken)) {
505            $headers['x-authenticationToken'] = $authenticationToken;
506        }
507        if (!empty($sessionToken)) {
508            $headers['x-sessionToken'] = $sessionToken;
509        }
510        return $headers;
511    }
512
513    /**
514     * Perform an HTTP request.
515     *
516     * @param string $baseUrl       Base URL for request
517     * @param string $method        HTTP method for request (GET, POST, etc.)
518     * @param string $queryString   Query string to append to URL
519     * @param array  $headers       HTTP headers to send
520     * @param string $messageBody   Message body to for HTTP Request
521     * @param string $messageFormat Format of request $messageBody and responses
522     * @param bool   $cacheable     Whether the request is cacheable
523     *
524     * @return string             HTTP response body
525     */
526    abstract protected function httpRequest(
527        $baseUrl,
528        $method,
529        $queryString,
530        $headers,
531        $messageBody,
532        $messageFormat,
533        $cacheable = true
534    );
535}