EBSCO Discovery module. Used on the library.upei.ca website. The bento box modules leverages the auth parts of this module.
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

<?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');
}
}