You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
698 lines
18 KiB
698 lines
18 KiB
<?php |
|
|
|
/** |
|
* @file |
|
* The EBSCO EDS API class. |
|
* |
|
* PHP version 5 |
|
* |
|
* |
|
* Copyright [2017] [EBSCO Information Services] |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
require_once 'EBSCOConnector.php'; |
|
require_once 'EBSCOResponse.php'; |
|
|
|
/** |
|
* EBSCO API class. |
|
*/ |
|
class EBSCOAPI { |
|
/** |
|
* The authentication token used for API transactions. |
|
* |
|
* @global string |
|
*/ |
|
private $authenticationToken; |
|
|
|
|
|
/** |
|
* The session token for API transactions. |
|
* |
|
* @global string |
|
*/ |
|
private $sessionToken; |
|
|
|
|
|
/** |
|
* The EBSCOConnector object used for API transactions. |
|
* |
|
* @global object EBSCOConnector |
|
*/ |
|
private $connector; |
|
|
|
|
|
/** |
|
* Configuration options. |
|
*/ |
|
private $config; |
|
|
|
/** |
|
* VuFind search types mapped to EBSCO search types |
|
* used for urls in search results / detailed result. |
|
* |
|
* @global array |
|
*/ |
|
private static $search_tags = array( |
|
'' => '', |
|
'AllFields' => '', |
|
'Abstract' => 'AB', |
|
'Author' => 'AU', |
|
'Source' => 'SO', |
|
'Subject' => 'SU', |
|
'Title' => 'TI', |
|
'ISBN' => 'IB', |
|
'ISSN' => 'IS', |
|
); |
|
|
|
|
|
/** |
|
* EBSCO sort options . |
|
* |
|
* @global array |
|
*/ |
|
private static $sort_options = array( |
|
'relevance', |
|
'date', |
|
'date2', |
|
'source', |
|
); |
|
|
|
|
|
/** |
|
* VuFind sort types mapped to EBSCO sort types |
|
* used for urls in Search results / Detailed view. |
|
* |
|
* @global array |
|
*/ |
|
private static $mapped_sort_options = array( |
|
'' => 'relevance', |
|
'relevance' => 'relevance', |
|
'subject' => 'date', |
|
'date' => 'date2', |
|
'date_asc' => 'date2', |
|
'date_desc' => 'date', |
|
'callnumber' => 'date', |
|
'author' => 'author', |
|
'title' => 'date', |
|
); |
|
|
|
/** |
|
* Constructor. |
|
* |
|
* @param array config |
|
* |
|
* @access public |
|
*/ |
|
public function __construct($config) { |
|
$this->config = $config; |
|
} |
|
|
|
/** |
|
* Setter / Getter for authentication token. |
|
* |
|
* @param string The authentication token |
|
* |
|
* @return string or none |
|
* |
|
* @access public |
|
*/ |
|
public function authenticationToken($token = NULL) { |
|
if (empty($token)) { |
|
$token = $this->readSession('authenticationToken'); |
|
return !empty($token) ? $token : $this->authenticationToken; |
|
} |
|
else { |
|
$this->authenticationToken = $token; |
|
$this->writeSession('authenticationToken', $token); |
|
} |
|
} |
|
|
|
/** |
|
* Setter / Getter for session token. |
|
* |
|
* @param string The session token |
|
* |
|
* @return string or none |
|
* |
|
* @access public |
|
*/ |
|
public function sessionToken($token = NULL) { |
|
if (empty($token)) { |
|
$token = $this->readSession('sessionToken'); |
|
return !empty($token) ? $token : $this->sessionToken; |
|
} |
|
else { |
|
$this->sessionToken = $token; |
|
$this->writeSession('sessionToken', $token); |
|
} |
|
} |
|
|
|
/** |
|
* Getter for isGuest. |
|
* |
|
* @param string 'y' or 'n' |
|
* |
|
* @return string or none |
|
* |
|
* @access public |
|
*/ |
|
public function isGuest($boolean = NULL) { |
|
if (empty($boolean)) { |
|
return $this->readSession('isGuest'); |
|
} |
|
else { |
|
$this->writeSession('isGuest', $boolean); |
|
} |
|
} |
|
|
|
/** |
|
* Create a new EBSCOConnector object or reuse an existing one. |
|
* |
|
* @param none |
|
* |
|
* @return EBSCOConnector object |
|
* |
|
* @access public |
|
*/ |
|
public function connector() { |
|
if (empty($this->connector)) { |
|
$this->connector = new EBSCOConnector($this->config); |
|
} |
|
return $this->connector; |
|
} |
|
|
|
/** |
|
* Create a new EBSCOResponse object. |
|
* |
|
* @param object $response |
|
* |
|
* @return EBSCOResponse object |
|
* |
|
* @access public |
|
*/ |
|
public function response($response) { |
|
$responseObj = new EBSCOResponse($response); |
|
return $responseObj; |
|
} |
|
|
|
/** |
|
* Request authentication and session tokens, then send the API request. |
|
* Retry the request if authentication errors occur. |
|
* |
|
* @param string $action |
|
* The EBSCOConnector method name. |
|
* @param array $params |
|
* The parameters of the HTTP request. |
|
* @param int $attempts |
|
* The number of retries. |
|
* |
|
* @return object SimpleXml DOM or PEAR Error |
|
* |
|
* @access protected |
|
*/ |
|
protected function request($action, $params = NULL, $attempts = 5) { |
|
$authenticationToken = $this->authenticationToken(); |
|
$sessionToken = $this->sessionToken(); |
|
|
|
// If authentication token is missing then the session token is missing too, so get both tokens |
|
// If session token is missing then the authentication token may be invalid, so get both tokens. |
|
if (empty($authenticationToken) || empty($sessionToken)) { |
|
$result = $this->apiAuthenticationAndSessionToken(); |
|
if ($this->isError($result)) { |
|
// Any error should terminate the request immediately |
|
// in order to prevent infinite recursion. |
|
return $result; |
|
} |
|
} |
|
|
|
// Any change of the isGuest should request a new session |
|
// (and don't terminate the current request if there was an error during the session request |
|
// since it's not that important) |
|
if ($this->isGuest() != $this->connector()->isGuest()) { |
|
$this->apiSessionToken(); |
|
} |
|
|
|
$headers = array( |
|
'x-authenticationToken' => $this->authenticationToken(), |
|
'x-sessionToken' => $this->sessionToken(), |
|
); |
|
|
|
$response = call_user_func_array(array($this->connector(), "request{$action}"), array($params, $headers)); |
|
if ($this->isError($response)) { |
|
// Retry the request if there were authentication errors. |
|
$code = $response->getCode(); |
|
switch ($code) { |
|
// If authentication token is invalid then the session token is invalid too, so get both tokens |
|
// If session token is invalid then the authentication token may be invalid too, so get both tokens. |
|
case EBSCOConnector::EDS_AUTH_TOKEN_INVALID: |
|
$result = $this->apiAuthenticationToken(); |
|
if ($this->isError($result)) { |
|
// Any error should terminate the request immediately |
|
// in order to prevent infinite recursion. |
|
return $result; |
|
} |
|
if ($attempts > 0) { |
|
$result = $this->request($action, $params, --$attempts); |
|
} |
|
break; |
|
|
|
case EBSCOConnector::EDS_SESSION_TOKEN_INVALID: |
|
$result = $this->apiAuthenticationAndSessionToken(); |
|
if ($this->isError($result)) { |
|
// Any error should terminate the request immediately |
|
// in order to prevent infinite recursion. |
|
return $result; |
|
} |
|
if ($attempts > 0) { |
|
$result = $this->request($action, $params, --$attempts); |
|
} |
|
break; |
|
|
|
default: |
|
$result = $this->handleError($response); |
|
break; |
|
} |
|
} |
|
else { |
|
$result = $this->response($response)->result(); |
|
} |
|
|
|
return $result; |
|
} |
|
|
|
/** |
|
* Wrapper for authentication API call. |
|
* |
|
* @param none |
|
* |
|
* @access public |
|
*/ |
|
public function apiAuthenticationToken() { |
|
$response = $this->connector()->requestAuthenticationToken(); |
|
|
|
if ($this->isError($response)) { |
|
return $response; |
|
} |
|
else { |
|
$result = $this->response($response)->result(); |
|
if (isset($result['authenticationToken'])) { |
|
$this->authenticationToken($result['authenticationToken']); |
|
return $result['authenticationToken']; |
|
} |
|
else { |
|
return new EBSCOException("No authentication token was found in the response."); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Wrapper for session API call. |
|
* |
|
* @param none |
|
* |
|
* @access public |
|
*/ |
|
public function apiSessionToken() { |
|
// Add authentication tokens to headers. |
|
$headers = array( |
|
'x-authenticationToken' => $this->authenticationToken(), |
|
); |
|
|
|
$response = $this->connector()->requestSessionToken($headers); |
|
// Raise the exception so that any code running this method should exit immediately. |
|
if ($this->isError($response)) { |
|
return $response; |
|
} |
|
else { |
|
$result = $this->response($response)->result(); |
|
if (is_string($result)) { |
|
$this->sessionToken($result); |
|
return $result; |
|
} |
|
else { |
|
return new EBSCOException("No session token was found in the response."); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Initialize the authentication and session tokens. |
|
* |
|
* @param none |
|
* |
|
* @access public |
|
*/ |
|
public function apiAuthenticationAndSessionToken() { |
|
$authenticationToken = $this->apiAuthenticationToken(); |
|
if ($this->isError($authenticationToken)) { |
|
// An authentication error should terminate the request immediately. |
|
return $authenticationToken; |
|
} |
|
|
|
$sessionToken = $this->apiSessionToken(); |
|
if ($this->isError($sessionToken)) { |
|
// A session error should terminate the request immediately. |
|
return $sessionToken; |
|
} |
|
|
|
// We don't have to return anything, both tokens can be accessed using the getters. |
|
return TRUE; |
|
} |
|
|
|
/** |
|
* Wrapper for search API call. |
|
* |
|
* @param array $search |
|
* The search terms. |
|
* @param array $filters |
|
* The facet filters. |
|
* @param string $start |
|
* The page to start with. |
|
* @param string $limit |
|
* The number of records to return. |
|
* @param string $sortBy |
|
* The value to be used by for sorting. |
|
* @param string $amount |
|
* The amount of data to be returned. |
|
* @param string $mode |
|
* The search mode. |
|
* |
|
* @throws object PEAR Error |
|
* |
|
* @return array An array of query results |
|
* |
|
* @access public |
|
*/ |
|
public function apiSearch($search, |
|
$filters, |
|
$start = 1, |
|
$limit = 10, |
|
$sortBy = 'relevance', |
|
$amount = 'detailed', |
|
$mode = 'all', |
|
$rs = FALSE, |
|
$emp = FALSE, |
|
$autosuggest = FALSE) { |
|
$query = array(); |
|
|
|
// Basic search. |
|
if (!empty($search['lookfor'])) { |
|
$lookfor = $search['lookfor']; |
|
$type = isset($search['index']) && !empty($search['index']) ? $search['index'] : 'AllFields'; |
|
|
|
// Escape some characters from lookfor term. |
|
$term = str_replace(array(',', ':', '(', ')'), array('\,', '\:', '\(', '\)'), $lookfor); |
|
// Replace multiple consecutive empty spaces with one empty space. |
|
$term = preg_replace("/\s+/", ' ', $term); |
|
|
|
// Search terms |
|
// Complex search term. |
|
if (preg_match('/(.*) (AND|OR) (.*)/i', $term)) { |
|
$query['query'] = $term; |
|
} |
|
else { |
|
$tag = self::$search_tags[$type]; |
|
$op = 'AND'; |
|
$query_str = implode(',', array($op, $tag)); |
|
$query_str = implode(($tag ? ':' : ''), array($query_str, $term)); |
|
$query['query-1'] = $query_str; |
|
} |
|
|
|
// Advanced search. |
|
} |
|
elseif (!empty($search['group'])) { |
|
|
|
$counter = 1; |
|
foreach ($search['group'] as $group) { |
|
$type = $group['type']; |
|
if (isset($group['lookfor'])) { |
|
$term = $group['lookfor']; |
|
$op = $group['bool']; |
|
$tag = $type && isset(self::$search_tags[$type]) ? self::$search_tags[$type] : ''; |
|
|
|
// Escape some characters from lookfor term. |
|
$term = str_replace(array(',', ':', '(', ')'), array('\,', '\:', '\(', '\)'), $term); |
|
// Replace multiple consecutive empty spaces with one empty space. |
|
$term = preg_replace("/\s+/", ' ', $term); |
|
if (!empty($term)) { |
|
$query_str = implode(',', array($op, $tag)); |
|
$query_str = implode(($tag ? ':' : ''), array($query_str, $term)); |
|
$query["query-$counter"] = $query_str; |
|
$counter++; |
|
} |
|
} |
|
} |
|
|
|
// No search term, return an empty array. |
|
} |
|
else { |
|
$results = array( |
|
'recordCount' => 0, |
|
'numFound' => 0, |
|
'start' => 0, |
|
'documents' => array(), |
|
'facets' => array(), |
|
); |
|
return $results; |
|
} |
|
|
|
// Add filters. |
|
$limiters = array(); $expanders = array(); $facets = array(); |
|
foreach ($filters as $filter) { |
|
if (preg_match('/addlimiter/', $filter)) { |
|
list($action, $str) = explode('(', $filter, 2); |
|
// e.g. FT:y or GZ:Student Research, Projects and Publications. |
|
$field_and_value = substr($str, 0, -1); |
|
list($field, $value) = explode(':', $field_and_value, 2); |
|
$limiters[$field][] = $value; |
|
} |
|
elseif (preg_match('/addexpander/', $filter)) { |
|
list($action, $str) = explode('(', $filter, 2); |
|
// Expanders don't have value. |
|
$field = substr($str, 0, -1); |
|
$expanders[] = $field; |
|
} |
|
elseif (preg_match('/addfacetfilter/', $filter)) { |
|
list($action, $str) = explode('(', $filter, 2); |
|
// e.g. ZG:FRANCE. |
|
$field_and_value = substr($str, 0, -1); |
|
list($field, $value) = explode(':', $field_and_value, 2); |
|
$facets[$field][] = $field_and_value; |
|
} |
|
} |
|
if (!empty($limiters)) { |
|
foreach ($limiters as $field => $limiter) { |
|
// e.g. LA99:English,French,German. |
|
$query['limiter'][] = $field . ':' . implode(',', $limiter); |
|
} |
|
} |
|
if (!empty($expanders)) { |
|
// e.g. fulltext, thesaurus. |
|
$query['expander'] = implode(',', $expanders); |
|
} |
|
if (!empty($facets)) { |
|
$groupId = 1; |
|
foreach ($facets as $field => $facet) { |
|
// e.g. 1,DE:Math,DE:History. |
|
$query['facetfilter'][] = $groupId . ',' . implode(',', $facet); |
|
$groupId += 1; |
|
} |
|
} |
|
|
|
// 2014-03-26 - new action to jump to page. |
|
if ($start > 1) { |
|
$query['action'] = "GoToPage(" . $start . ")"; |
|
} |
|
|
|
// Add the sort option. |
|
$sortBy = in_array($sortBy, self::$sort_options) ? $sortBy : self::$mapped_sort_options[$sortBy]; |
|
|
|
// Add the HTTP query params. |
|
$params = array( |
|
// Specifies the sort. Valid options are: |
|
// relevance, date, date2 |
|
// date = Date descending |
|
// date2 = Date ascending. |
|
'sort' => $sortBy, |
|
// Specifies the search mode. Valid options are: |
|
// bool, any, all, smart. |
|
'searchmode' => $mode, |
|
// Specifies the amount of data to return with the response |
|
// Valid options are: |
|
// title: Title only |
|
// brief: Title + Source, Subjects |
|
// detailed: Brief + full abstract. |
|
'view' => $amount, |
|
// Specifies whether or not to include facets. |
|
'includefacets' => 'y', |
|
'resultsperpage' => $limit, |
|
|
|
// 2014-03-26 RF. |
|
'pagenumber' => $start, |
|
// 'pagenumber' => 1, |
|
// Specifies whether or not to include highlighting in the search results. |
|
'highlight' => 'y', |
|
); |
|
|
|
if ($autosuggest == TRUE) { |
|
$params["autosuggest"] = "y"; |
|
} |
|
|
|
if ($rs == TRUE) { |
|
$params["relatedcontent"] = "rs"; |
|
} |
|
|
|
if ($emp == TRUE) { |
|
if (isset($params["relatedcontent"])) { |
|
$params["relatedcontent"] .= ",emp"; |
|
} |
|
else { |
|
$params["relatedcontent"] = "emp"; |
|
} |
|
} |
|
|
|
$params = array_merge($params, $query); |
|
|
|
$result = $this->request('Search', $params); |
|
return $result; |
|
} |
|
|
|
/** |
|
* Wrapper for retrieve API call. |
|
* |
|
* @param array $an |
|
* The accession number. |
|
* @param string $start |
|
* The short database name. |
|
* |
|
* @throws object PEAR Error |
|
* |
|
* @return array An associative array of data |
|
* |
|
* @access public |
|
*/ |
|
public function apiRetrieve($an, $db) { |
|
// Add the HTTP query params. |
|
$params = array( |
|
'an' => $an, |
|
'dbid' => $db, |
|
'highlight' => 'y', |
|
); |
|
|
|
$result = $this->request('Retrieve', $params); |
|
return $result; |
|
} |
|
|
|
/** |
|
* Wrapper for info API call. |
|
* |
|
* @throws object PEAR Error |
|
* |
|
* @return array An associative array of data |
|
* |
|
* @access public |
|
*/ |
|
public function apiInfo() { |
|
if ($result = $this->readSession('info')) { |
|
return $result; |
|
} |
|
$result = $this->request('Info'); |
|
|
|
if (!$this->isError($result)) { |
|
$this->writeSession('info', $result); |
|
} |
|
|
|
return $result; |
|
} |
|
|
|
/** |
|
* Handle a PEAR_Error. Return : |
|
* - if the error is critical : an associative array with the current error message |
|
* - if the error is not critical : the error message . |
|
* |
|
* @param Pear_Error $exception |
|
* |
|
* @return array or the Pear_Error exception |
|
* |
|
* @access protected |
|
*/ |
|
private function handleError($error) { |
|
$errorCode = $error->getCode(); |
|
switch ($errorCode) { |
|
// This kind of error was generated by user , so display it to user. |
|
case EBSCOConnector::EDS_INVALID_ARGUMENT_VALUE: |
|
// Any other errors are system errors, don't display them to user. |
|
default: |
|
$errorMessage = 'An error occurred when getting the data.'; |
|
break; |
|
} |
|
$result = array( |
|
'errors' => $errorMessage, |
|
'recordCount' => 0, |
|
'numFound' => 0, |
|
'start' => 0, |
|
'documents' => array(), |
|
'facets' => array(), |
|
); |
|
return $result; |
|
} |
|
|
|
/** |
|
* Store the given object into session. |
|
* |
|
* @param string $key |
|
* The key used for reading the value. |
|
* @param object $value |
|
* The object stored in session. |
|
* |
|
* @return none |
|
* |
|
* @access protected |
|
*/ |
|
protected function writeSession($key, $value) { |
|
if (!empty($key) && !empty($value)) { |
|
$_SESSION['EBSCO'][$key] = $value; |
|
} |
|
} |
|
|
|
/** |
|
* Read from session the object having the given key. |
|
* |
|
* @param string $key |
|
* The key used for reading the object. |
|
* |
|
* @return object |
|
* |
|
* @access protected |
|
*/ |
|
protected function readSession($key) { |
|
$value = isset($_SESSION['EBSCO'][$key]) ? $_SESSION['EBSCO'][$key] : ''; |
|
return $value; |
|
} |
|
|
|
/** |
|
* Check if given object is an EBSCOException object. |
|
* |
|
* @param object $object |
|
* |
|
* @return bool |
|
* |
|
* @access protected |
|
*/ |
|
protected function isError($object) { |
|
return is_a($object, 'EBSCOException'); |
|
} |
|
|
|
}
|
|
|