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