From d27cd63c62150636ce3e4f253e14c9c8193e93d8 Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Fri, 26 Jul 2013 11:41:47 -0400 Subject: [PATCH] Aleph driver improvements courtesy of Vaclav Rosecky: - removed dependency on curl library by using HttpServiceAwareInterface - logging using LoggerAwareInterface - deprecated function split replaced by explode - implementation of getDefaultPickUpLocation method - use of DateTime class for parsing dates --- config/vufind/Aleph.ini | 6 +- module/VuFind/src/VuFind/ILS/Driver/Aleph.php | 245 +++++++++++++----- 2 files changed, 188 insertions(+), 63 deletions(-) diff --git a/config/vufind/Aleph.ini b/config/vufind/Aleph.ini index fdb1f021d3b..c70181f6d08 100644 --- a/config/vufind/Aleph.ini +++ b/config/vufind/Aleph.ini @@ -1,5 +1,4 @@ ; NOTE: -; PHP curl library is required by Aleph driver ; XServer is required only for authentication. If you don't have it, use other authentication mechanism (LDAP, Shibboleth). ; ; URL http://host:dlfport/rest-dlf/ should return xml like this: @@ -40,10 +39,13 @@ admlib = MZK50 ;wwwpasswd = "YOUR-WWW-X-USER-PASSWORD" ; Comma-separated list of statuses when an item is available for loan -available_statuses = "On Shelf,Open St.-Month,Vol.výb.-mes." +available_statuses = "On Shelf,Open St.-Month,Free-Stack" ; If enabled and Xserver is disabled, Aleph driver will use slower RESTful API for availability check. quick_availability = true +; Comma-separated list of pickup locations sorted by preference +preferred_pick_up_locations = "PICK1,PICK2" + ; adm-lib / sub-library array ; This is a list of patron home libraries and the ADM the library belongs to [sublibadm] diff --git a/module/VuFind/src/VuFind/ILS/Driver/Aleph.php b/module/VuFind/src/VuFind/ILS/Driver/Aleph.php index 16c7cbbad84..70ccc72ee7b 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Aleph.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Aleph.php @@ -37,6 +37,9 @@ */ namespace VuFind\ILS\Driver; use VuFind\Exception\ILS as ILSException; +use Zend\Log\LoggerInterface; +use VuFindHttp\HttpServiceInterface; +use DateTime; /** * Aleph Translator Class @@ -252,6 +255,35 @@ class AlephTranslator */ class AlephRestfulException extends \Exception { + /** + * XML response (false for none) + * + * @var string|bool + */ + protected $xmlResponse = false; + + /** + * Attach an XML response to the exception + * + * @param string $body XML + * + * @return void + */ + public function setXmlResponse($body) + { + $this->xmlResponse = $body; + } + + /** + * Return XML response (false if none) + * + * @return string|bool + */ + public function getXmlResponse() + { + return $this->xmlResponse; + } + } /** @@ -267,9 +299,21 @@ class AlephRestfulException extends \Exception * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link http://vufind.org/wiki/vufind2:building_an_ils_driver Wiki */ -class Aleph extends AbstractBase +class Aleph extends AbstractBase implements \Zend\Log\LoggerAwareInterface, + \VuFindHttp\HttpServiceAwareInterface { + /** + * Duedate configuration + * + * @var array + */ protected $duedates = false; + + /** + * Translator object + * + * @var AlephTranslator + */ protected $translator = false; /** @@ -279,6 +323,20 @@ class Aleph extends AbstractBase */ protected $cacheManager; + /** + * Logger object for debug info (or false for no debugging). + * + * @var LoggerInterface|bool + */ + protected $logger = false; + + /** + * HTTP service + * + * @var \VuFindHttp\HttpServiceInterface + */ + protected $httpService = null; + /** * Constructor * @@ -289,6 +347,30 @@ class Aleph extends AbstractBase $this->cacheManager = $cacheManager; } + /** + * Set the logger + * + * @param LoggerInterface $logger Logger to use. + * + * @return void + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Set the HTTP service to be used for HTTP requests. + * + * @param HttpServiceInterface $service HTTP service + * + * @return void + */ + public function setHttpService(HttpServiceInterface $service) + { + $this->httpService = $service; + } + /** * Initialize the driver. * @@ -315,7 +397,7 @@ class Aleph extends AbstractBase // Process config $this->host = $this->config['Catalog']['host']; - $this->bib = split(',', $this->config['Catalog']['bib']); + $this->bib = explode(',', $this->config['Catalog']['bib']); $this->useradm = $this->config['Catalog']['useradm']; $this->admlib = $this->config['Catalog']['admlib']; if (isset($this->config['Catalog']['wwwuser']) @@ -333,7 +415,7 @@ class Aleph extends AbstractBase $this->duedates = $this->config['duedates']; } $this->available_statuses - = split(',', $this->config['Catalog']['available_statuses']); + = explode(',', $this->config['Catalog']['available_statuses']); $this->quick_availability = isset($this->config['Catalog']['quick_availability']) ? $this->config['Catalog']['quick_availability'] : false; @@ -357,6 +439,11 @@ class Aleph extends AbstractBase } } } + if (isset($this->config['Catalog']['preferred_pick_up_locations'])) { + $this->preferredPickUpLocations = explode( + ',', $this->config['Catalog']['preferred_pick_up_locations'] + ); + } } /** @@ -420,7 +507,9 @@ class Aleph extends AbstractBase $replyCode = (string) $result->{'reply-code'}; if ($replyCode != "0000") { $replyText = (string) $result->{'reply-text'}; - throw new AlephRestfulException($replyText, $replyCode); + $ex = new AlephRestfulException($replyText, $replyCode); + $ex->setXmlResponse($result); + throw $ex; } return $result; } @@ -459,28 +548,25 @@ class Aleph extends AbstractBase if ($this->debug_enabled) { $this->debug("URL: '$url'"); } - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - if ($body != null) { - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - } - $answer = curl_exec($ch); - if (!$answer) { - $error = curl_error($ch); - $message = "HTTP request failed with message: $error, URL: '$url'."; - if ($this->debug_enabled) { - $this->debug($message); + + $result = null; + try { + $client = $this->httpService->createClient($url); + $client->setMethod($method); + if ($body != null) { + $client->setRawBody($body); } - throw new ILSException($message); + $result = $client->send(); + } catch (\Exception $e) { + throw new ILSException($e->getMessage()); + } + if (!$result->isSuccess()) { + throw new ILSException('HTTP error'); } - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if ($http_code != 200) { - $message = "Request failed with http code: $http_code, " - . "URL: '$url' method: $method"; - throw new ILSException($message); + $answer = $result->getBody(); + if ($this->debug_enabled) { + $this->debug("url: $url response: $answer"); } - curl_close($ch); $answer = str_replace('xmlns=', 'ns=', $answer); $result = simplexml_load_string($answer); if (!$result) { @@ -503,7 +589,9 @@ class Aleph extends AbstractBase */ protected function debug($msg) { - print($msg . "<BR>"); + if ($this->logger) { + $this->logger->debug($msg); + } } /** @@ -518,7 +606,7 @@ class Aleph extends AbstractBase if (count($this->bib)==1) { return array($this->bib[0], $id); } else { - return split('-', $id); + return explode('-', $id); } } @@ -897,13 +985,24 @@ class Aleph extends AbstractBase public function renewMyItems($details) { $patron = $details['patron']; + $error = false; + $result = array(); foreach ($details['details'] as $id) { - $this->doRestDLFRequest( - array('patron', $patron['id'], 'circulationActions', 'loans', $id), - null, 'POST', null - ); + try { + $this->doRestDLFRequest( + array( + 'patron', $patron['id'], 'circulationActions', 'loans', $id + ), + null, 'POST', null + ); + $result[$id] = array('success' => true); + } catch (AlephRestfulException $ex) { + $result[$id] = array( + 'success' => false, 'sysMessage' => $ex->getMessage() + ); + } } - return array('blocks' => false, 'details' => array()); + return array('blocks' => false, 'details' => $result); } /** @@ -1170,7 +1269,7 @@ class Aleph extends AbstractBase $credit_sign = (string) $xml->z305->{'z305-credit-debit'}; $name = (string) $xml->z303->{'z303-name'}; if (strstr($name, ",")) { - list($lastname, $firstname) = split(",", $name); + list($lastname, $firstname) = explode(",", $name); } else { $lastname = $name; $firstname = ""; @@ -1227,7 +1326,7 @@ class Aleph extends AbstractBase $recordList['firstname'] = ""; } else { list($recordList['lastname'], $recordList['firstname']) - = split(",", $address2); + = explode(",", $address2); } $recordList['address1'] = $address2; $recordList['address2'] = $address3; @@ -1281,7 +1380,7 @@ class Aleph extends AbstractBase $patron=array(); $name = $xml->z303->{'z303-name'}; if (strstr($name, ",")) { - list($lastName, $firstName) = split(",", $name); + list($lastName, $firstName) = explode(",", $name); } else { $lastName = $name; $firstName = ""; @@ -1339,7 +1438,7 @@ class Aleph extends AbstractBase $requests = 0; $str = $xml->xpath('//item/queue/text()'); if ($str != null) { - list($requests, $other) = split(' ', trim($str[0])); + list($requests, $other) = explode(' ', trim($str[0])); } $date = $xml->xpath('//last-interest-date/text()'); $date = $date[0]; @@ -1369,24 +1468,27 @@ class Aleph extends AbstractBase list($bib, $sys_no) = $this->parseId($details['id']); $recordId = $bib . $sys_no; $itemId = $details['item_id']; - $pickup_location = $details['pickUpLocation']; $patron = $details['patron']; - $requiredBy = $details['requiredBy']; + $pickupLocation = $details['pickUpLocation']; + if (!$pickupLocation) { + $pickupLocation = $this->getDefaultPickUpLocation($patron, $details); + } $comment = $details['comment']; - list($month, $day, $year) = split("-", $requiredBy); - $requiredBy = $year . str_pad($month, 2, "0", STR_PAD_LEFT) - . str_pad($day, 2, "0", STR_PAD_LEFT); - $patronId = $patron['id']; - if (!$pickup_location) { - $info = $this->getHoldingInfoForItem($patronId, $recordId, $itemId); - // FIXME: choose preffered location - foreach ($info['pickup-locations'] as $key => $value) { - $pickup_location = $key; - } + // convert from MM-DD-YYYY to YYYYMMDD required by Aleph + $requiredBy = $details['requiredBy']; + $requiredBy = DateTime::createFromFormat('n-j-Y', $requiredBy); + $dtErrs = DateTime::getLastErrors(); + if ($requiredBy === false || $dtErrs['warning_count'] > 0 ) { + return array( + 'success' => false, + 'sysMessage' => 'requiredBy must be in MM-DD-YYYY format' + ); } + $requiredBy = $requiredBy->format('Ymd'); + $patronId = $patron['id']; $data = "post_xml=<?xml version='1.0' encoding='UTF-8'?>\n" . "<hold-request-parameters>\n" . - " <pickup-location>$pickup_location</pickup-location>\n" . + " <pickup-location>$pickupLocation</pickup-location>\n" . " <last-interest-date>$requiredBy</last-interest-date>\n" . " <note-1>$comment</note-1>\n". "</hold-request-parameters>\n"; @@ -1451,22 +1553,19 @@ class Aleph extends AbstractBase */ public function parseDate($date) { - if ($date == "") { + if ($date == null || $date == "") { return ""; } else if (preg_match("/^[0-9]{8}$/", $date) === 1) { - return substr($date, 6, 2) . "." .substr($date, 4, 2) . "." - . substr($date, 0, 4); + // 20120725 + return DateTime::createFromFormat('Ynd', $date)->format('d.m.Y'); + } else if (preg_match("/^[0-9]+\/[A-Za-z]{3}\/[0-9]{4}$/", $date) === 1) { + // 13/jan/2012 + return DateTime::createFromFormat('d/M/Y', $date)->format('d.m.Y'); + } else if (preg_match("/^[0-9]+\/[0-9]+\/[0-9]{4}$/", $date) === 1) { + // 13/7/2012 + return DateTime::createFromFormat('d/m/Y', $date)->format('d.m.Y'); } else { - list($day, $month, $year) = explode("/", $date, 3); - if (!is_numeric($month)) { - $translate_month = array( - 'jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, - 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, - 'nov' => 11, 'dec' => 12 - ); - $month = $translate_month[strtolower($month)]; - } - return $day . "." . $month . "." . $year; + throw new \Exception("Invalid date: $date"); } } @@ -1542,7 +1641,31 @@ class Aleph extends AbstractBase */ public function getDefaultPickUpLocation($patron, $holdInfo=null) { - return null; + if ($holdInfo != null) { + $details = $this->getHoldingInfoForItem( + $patron['id'], $holdInfo['id'], $holdInfo['item_id'] + ); + $pickupLocations = $details['pickup-locations']; + if (isset($this->preferredPickUpLocations)) { + foreach ( + $details['pickup-locations'] as $locationID => $locationDisplay + ) { + if (in_array($locationID, $this->preferredPickUpLocations)) { + return $locationID; + } + } + } + // nothing found or preferredPickUpLocations is empty? Return the first + // locationId in pickupLocations array + reset($pickupLocations); + return key($pickupLocations); + } else if (isset($this->preferredPickUpLocations)) { + return $this->preferredPickUpLocations[0]; + } else { + throw new ILSException( + 'Missing Catalog/preferredPickUpLocations config setting.' + ); + } } /** -- GitLab