EBSCO Discovery module. Used on the library.upei.ca website. The bento box modules leverages the auth parts of this module.

699 lines
18 KiB

<?php
/**
* @file
* The EBSCO EDS API class.
*
* PHP version 5
*
*
* Copyright [2017] [EBSCO Information Services]
10 years ago
*
* 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
*
10 years ago
* 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');
}
}