From a4db008e2f6506e4e067c7cec5df0d60f3b7d37d Mon Sep 17 00:00:00 2001
From: Thomas Misilo <misilot@fit.edu>
Date: Fri, 24 Jul 2015 09:05:32 -0400
Subject: [PATCH] Adding Original Files provided from Josef Moravec

---
 config/vufind/KohaRest.ini                    |   43 +
 .../VuFind/src/VuFind/ILS/Driver/KohaRest.php | 1350 +++++++++++++++++
 2 files changed, 1393 insertions(+)
 create mode 100755 config/vufind/KohaRest.ini
 create mode 100644 module/VuFind/src/VuFind/ILS/Driver/KohaRest.php

diff --git a/config/vufind/KohaRest.ini b/config/vufind/KohaRest.ini
new file mode 100755
index 00000000000..1a441a86ef2
--- /dev/null
+++ b/config/vufind/KohaRest.ini
@@ -0,0 +1,43 @@
+[Catalog]
+; database host, port, user, password, database
+host        = localhost
+port        = 3306
+username    = mysqlusername
+password    = mysqlpassword
+database    = koha
+
+; url to ilsdi api
+url         	= http://library.myuniversity.edu/cgi-bin/koha/ilsdi.pl
+
+debug       	= false
+
+;; In addition you can set 'renewals_enabled' and
+;; 'cancel_holds_enabled' in config.ini to 'true' using this driver.
+;; I would also recommend you set 'holds_mode' to '"holds"', as this
+;; driver does not handle recalls.
+
+[Holds]
+; HMACKeys - A list of hold form element names that will be analyzed for consistency
+; during hold form processing. Most users should not need to change this setting.
+; Comment this line to disable VuFind integrated reservations.
+HMACKeys = item_id:id:level
+
+; defaultRequiredDate - A colon-separated list used to set the default "not required
+; after" date for holds in the format days:months:years
+; e.g. 0:1:0 will set a "not required after" date of 1 month from the current date
+defaultRequiredDate = 0:0:1
+
+; extraHoldFields - A colon-separated list used to display extra visible fields in the
+; place holds form. Supported values are "comments", "requiredByDate" and 
+; "pickUpLocation"  
+extraHoldFields = pickUpLocation:requiredByDate
+
+; A Pick Up Location Code used to pre-select the pick up location drop
+; down list and provide a default option if others are not
+; available. The default of 'false' will force users to pick a pickup
+; location. By setting this to a Koha location code (e.g. '"MAIN"'),
+; Vufind will default to that location.
+defaultPickUpLocation = "MAIN"
+
+; branchcodes for libraries avalaible as pickup locations
+pickupLocations[] = MAIN
diff --git a/module/VuFind/src/VuFind/ILS/Driver/KohaRest.php b/module/VuFind/src/VuFind/ILS/Driver/KohaRest.php
new file mode 100644
index 00000000000..b26afa9c7e4
--- /dev/null
+++ b/module/VuFind/src/VuFind/ILS/Driver/KohaRest.php
@@ -0,0 +1,1350 @@
+<?php
+/**
+ * KohaRest ILS Driver 
+*
+ * PHP version 5
+ *
+ * Copyright (C) Alex Sassmannshausen, PTFS Europe 2014.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  ILS_Drivers
+ * @author   Alex Sassmannshausen, <alex.sassmannshausen@ptfs-europe.com>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:building_an_ils_driver Wiki
+ */
+namespace KatalogCT\ILS\Driver;
+use PDO, PDOException;
+use VuFind\Exception\ILS as ILSException;
+use VuFindHttp\HttpServiceInterface;
+use Zend\Log\LoggerInterface;
+use VuFind\Exception\Date as DateException;
+
+/**
+ * VuFind Driver for Koha, using web APIs (version: 0.1)
+ *
+ * last updated: 05/13/2014
+ *
+ * @category VuFind2
+ * @package  ILS_Drivers
+ * @author   Alex Sassmannshausen, <alex.sassmannshausen@ptfs-europe.com>
+ * @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 KohaRest extends \VuFind\ILS\Driver\AbstractBase implements \VuFindHttp\HttpServiceAwareInterface,
+    \Zend\Log\LoggerAwareInterface
+{
+    /**
+     * Web services host
+     *
+     * @var string
+     */
+    protected $host;
+
+    /**
+     * Web services application path
+     *
+     * @var string
+     */
+    //protected $api_path = "/cgi-bin/koha/ilsdi.pl?service=";
+
+    /**
+     * ILS base URL
+     *
+     * @var string
+     */
+    protected $ilsBaseUrl;
+
+    /**
+     * Location codes
+     *
+     * @var array
+     */
+    protected $locations;
+    
+    /**
+     * Codes of locations avalaible for pickup
+     *
+     * @var array
+     */
+    protected $pickupEnableBranchcodes;
+
+    /**
+     * Default location code
+     *
+     * @var string
+     */
+    protected $default_location;
+
+    /**
+     * Database connection
+     *
+     * @var string
+     */
+    
+    protected $db;
+    
+    /**
+     * Set the logger
+     *
+     * @param LoggerInterface $logger Logger to use.
+     *
+     * @return void
+     */
+    
+    protected $logger = false;
+    
+    public function setLogger(LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * Show a debug message.
+     *
+     * @param string $msg Debug message.
+     *
+     * @return void
+     */
+    protected function debug($msg)
+    {
+        if ($this->logger) {
+            $this->logger->debug($msg);
+        }
+    }
+
+    /**
+     * HTTP service
+     *
+     * @var \VuFindHttp\HttpServiceInterface
+     */
+    protected $httpService = null;
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Date converter object
+     *
+     * @var \VuFind\Date\Converter
+     */
+    protected $dateConverter;
+
+    /**
+     * Initialize the driver.
+     *
+     * Validate configuration and perform all resource-intensive tasks needed to
+     * make the driver active.
+     *
+     * @throws ILSException
+     * @return void
+     */
+    public function init()
+    {
+        if (empty($this->config)) {
+            throw new ILSException('Configuration needs to be set.');
+        }
+
+        // Is debugging enabled?
+        $this->debug_enabled = isset($this->config['Catalog']['debug'])
+            ? $this->config['Catalog']['debug'] : false;
+
+        // Base for API address
+        $this->host = isset($this->config['Catalog']['host']) ?
+            $this->config['Catalog']['host'] : "localhost";
+
+        // Storing the base URL of ILS
+        $this->ilsBaseUrl = isset($this->config['Catalog']['url'])
+        	? $this->config['Catalog']['url'] : "";
+
+        // Default location defined in 'KohaRest.ini'
+        $this->default_location
+            = isset($this->config['Holds']['defaultPickUpLocation'])
+            ? $this->config['Holds']['defaultPickUpLocation'] : null;
+
+        $this->pickupEnableBranchcodes 
+        	= isset($this->config['Holds']['pickupLocations'])
+        	? $this->config['Holds']['pickupLocations'] : array();
+        
+        // Create a dateConverter
+        $this->dateConverter = new \VuFind\Date\Converter;
+
+        if ($this->debug_enabled) {
+            $this->debug("Config Summary:");
+            $this->debug("Debug: " . $this->debug_enabled);
+            $this->debug("DB Host: " . $this->host);
+            $this->debug("ILS URL: " . $this->ilsBaseUrl);
+            $this->debug("Locations: " . $this->locations);
+            $this->debug("Default Location: " . $this->default_location);
+        }
+    }
+    
+    /**
+     * Initialize the DB driver.
+     *
+     * Validate configuration and perform all resource-intensive tasks needed to
+     * make the driver active.
+     *
+     * @throws ILSException
+     * @return void
+     */
+    public function initDB()
+    {           
+      if (empty($this->config)) {
+        throw new ILSException('Configuration needs to be set.');
+      }
+      
+      //Connect to MySQL
+      try {
+        $this->db = new PDO(
+        		'mysql:host=' . $this->host .
+        		';port=' . $this->config['Catalog']['port'] .
+        		';dbname=' . $this->config['Catalog']['database'],
+        		$this->config['Catalog']['username'],
+        		$this->config['Catalog']['password']
+        );
+        
+        // Throw PDOExceptions if something goes wrong
+        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+        //Return result set like mysql_fetch_assoc()
+        $this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+		// set communication enoding to utf8
+        $this->db->exec("SET NAMES utf8");
+      } catch (PDOException $e) {
+      	echo 'Connection failed: ' . $e->getMessage();
+        $this->debug('Connection failed: ' . $e->getMessage());
+      }
+      
+      $this->debug('Connected to DB');
+    }
+
+    /**
+     * getField 
+     *
+     * Check $contents is not "", return it; else return $default.
+     *
+     * @param string $contents string to be checked
+     * @param string $default  value to return if $contents is ""
+     *
+     * @return $contents or $default
+     */
+    protected function getField($contents, $default="Unknown")
+    {
+        if ((string) $contents != "") {
+            return (string) $contents;
+        } else {
+            return $default;
+        }
+    }
+
+    /**
+     * Make Request
+     *
+     * Makes a request to the Polaris Restful API
+     *
+     * @param string $api_query   Query string for request
+     * @param string $http_method HTTP method (default = GET)
+     *
+     * @throws ILSException
+     * @return obj
+     */
+    protected function makeRequest($api_query, $http_method="GET")
+    {
+        //$url = $this->host . $this->api_path . $api_query;
+
+        $url = $this->ilsBaseUrl . "?service=" . $api_query;
+        
+        if ($this->debug_enabled) {
+            $this->debug("URL: '$url'");
+        }
+        $http_headers = array(
+            "Accept: text/xml",
+            "Accept-encoding: plain",
+        );
+
+        try {
+            $client = $this->httpService->createClient($url);
+
+            $client->setMethod($http_method);
+            $client->setHeaders($http_headers);
+            $result = $client->send();
+        } catch (\Exception $e) {
+            $this->debug("Result is invalid.");
+            throw new ILSException($e->getMessage());
+        }
+
+        if (!$result->isSuccess()) {
+            $this->debug("Result is invalid.");
+            throw new ILSException('HTTP error');
+        }
+        $answer = $result->getBody();
+        //$answer = str_replace('xmlns=', 'ns=', $answer);
+        $result = simplexml_load_string($answer);
+        if (!$result) {
+            if ($this->debug_enabled) {
+                $this->debug("XML is not valid, URL: $url");
+            }
+            throw new ILSException(
+                "XML is not valid, URL: $url method: $method answer: $answer."
+            );
+        }
+        return $result;
+    }
+
+    /**
+     * Make Ilsdi Request Array
+     *
+     * Makes a request to the Polaris Restful API
+     *
+     * @param string $service   Called function (GetAvailability, GetRecords, GetAuthorityRecords, LookupPatron, 
+     * 								AuthenticatePatron, GetPatronInfo, GetPatronStatus, GetServices, RenewLoan, 
+	 * 								HoldTitle, HoldItem, CancelHold)
+	 * @param array $params		Params for the function, key is parameter name, value is parameter value 
+     * @param string $http_method HTTP method (default = GET)
+     *
+     * @throws ILSException
+     * @return obj
+     */
+    protected function makeIlsdiRequest($service, $params, $http_method="GET")
+    {
+    	$start = microtime(true);
+    	$url = $this->ilsBaseUrl . "?service=" . $service;
+    	foreach ($params as $paramname => $paramvalue) {
+    		$url .= "&$paramname=" . urlencode($paramvalue);
+    	}
+    
+    	if ($this->debug_enabled) {
+    		$this->debug("URL: '$url'");
+    	}
+    	$http_headers = array(
+    			"Accept: text/xml",
+    			"Accept-encoding: plain",
+    	);
+    
+    	try {
+    		$client = $this->httpService->createClient($url);
+    		$client->setMethod($http_method);
+    		$client->setHeaders($http_headers);
+    		$result = $client->send();
+    	} catch (\Exception $e) {
+    		$this->debug("Result is invalid.");
+    		throw new ILSException($e->getMessage());
+    	}
+    
+    	if (!$result->isSuccess()) {
+    		$this->debug("Result is invalid.");
+    		throw new ILSException('HTTP error');
+    	}
+    	$end = microtime(true);
+    	$time1 = $end - $start;
+    	$start = microtime(true);
+    	$result = simplexml_load_string($result->getBody());
+    	if (!$result) {
+    		if ($this->debug_enabled) {
+    			$this->debug("XML is not valid, URL: $url");
+    		}
+    		throw new ILSException(
+    				"XML is not valid, URL: $url"
+    		);
+    	}
+    	$end = microtime(true);
+    	$time2 = $end - $start;
+    	echo "\t$time1 - $time2";
+    	return $result;
+    }
+    
+    
+    /**
+     * toKohaDate
+     *
+     * Turns a display date into a date format expected by Koha.
+     *
+     * @param string $display_date Date to be converted
+     *
+     * @throws ILSException
+     * @return string $koha_date
+     */
+    protected function toKohaDate($display_date)
+    {
+        $koha_date = "";
+
+        // Convert last interest date from format to Koha format
+        $koha_date = $this->dateConverter->convertFromDisplayDate(
+            "Y-m-d", $display_date
+        );
+
+        $checkTime =  $this->dateConverter->convertFromDisplayDate(
+            "U", $display_date
+        );
+        if (!is_numeric($checkTime)) {
+            throw new DateException('Result should be numeric');
+        }
+
+        if (time() > $checkTime) {
+            // Hold Date is in the past
+            throw new DateException('hold_date_past');
+        }
+        return $koha_date;
+    }
+
+    /**
+     * Public Function which retrieves renew, hold and cancel settings from the
+     * driver ini file.
+     *
+     * @param string $function The name of the feature to be checked
+     *
+     * @return array An array with key-value pairs.
+     */
+    public function getConfig($function)
+    {
+        $functionConfig = "";
+        if (isset($this->config[$function])) {
+            $functionConfig = $this->config[$function];
+        } else {
+            $functionConfig = false;
+        }
+        return $functionConfig;
+    }
+
+    /**
+     * Get Pick Up Locations
+     *
+     * This is responsible for gettting a list of valid library locations for
+     * holds / recall retrieval
+     *
+     * @param array $patron      Patron information returned by the patronLogin
+     * method.
+     * @param array $holdDetails Optional array, only passed in when getting a list
+     * in the context of placing a hold; contains most of the same values passed to
+     * placeHold, minus the patron data.    May be used to limit the pickup options
+     * or may be ignored.  The driver must not add new options to the return array
+     * based on this data or other areas of VuFind may behave incorrectly.
+     *
+     * @throws ILSException
+     * @return array             An array of associative arrays with locationID
+     * and locationDisplay keys
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function getPickUpLocations($patron = false, $holdDetails = null)
+    {
+    	if (!$this->locations) {
+    		if (!$this->db) {
+    			$this->initDB();
+    		}
+    		$branchcodes = "'" . implode("','", $this->pickupEnableBranchcodes) . "'";
+    		$sql = "SELECT branchcode as locationID, branchname as locationDisplay FROM branches WHERE branchcode IN ($branchcodes)";
+    		try {
+    			$sqlSt = $this->db->prepare($sql);
+    			$sqlSt->execute();
+    			$this->locations = $sqlSt->fetchAll();
+    		} catch (PDOException $e) {
+        		$this->debug('Connection failed: ' . $e->getMessage());
+        		throw new ILSException($e->getMessage());
+      		}
+    	}
+      	return $this->locations;
+
+            // we get them from the API
+            // FIXME: Not yet possible: API incomplete.
+            // TODO: When API: pull locations dynamically from API.
+            /* $response = $this->makeRequest("organizations/branch"); */
+            /* $locations_response_array = $response->OrganizationsGetRows; */
+            /* foreach ($locations_response_array as $location_response) { */
+            /*     $locations[] = array( */
+            /*         'locationID'      => $location_response->OrganizationID, */
+            /*         'locationDisplay' => $location_response->Name, */
+            /*     ); */
+            /* } */
+    }
+
+    /**
+     * Get Default Pick Up Location
+     *
+     * Returns the default pick up location set in KohaRest.ini
+     *
+     * @param array $patron      Patron information returned by the patronLogin
+     * method.
+     * @param array $holdDetails Optional array, only passed in when getting a list
+     * in the context of placing a hold; contains most of the same values passed to
+     * placeHold, minus the patron data.    May be used to limit the pickup options
+     * or may be ignored.
+     *
+     * @return string           The default pickup location for the patron.
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function getDefaultPickUpLocation($patron = false, $holdDetails = null)
+    {
+        return $this->default_location;
+    }
+
+    /**
+     * Place Hold
+     *
+     * Attempts to place a hold or recall on a particular item and returns
+     * an array with result details or throws an exception on failure of support
+     * classes
+     *
+     * @param array $holdDetails An array of item and patron data
+     *
+     * @throws ILSException
+     * @return mixed An array of data on the request including
+     * whether or not it was successful and a system message (if available)
+     */
+    public function placeHold($holdDetails)
+    {
+        $rsvLst             = array();
+        $patron             = $holdDetails['patron'];
+        $patron_id          = $patron['id'];
+        $request_location   = isset($patron['ip']) ? $patron['ip'] : "127.0.0.1";
+        $bib_id             = $holdDetails['id'];
+        $item_id            = $holdDetails['item_id'];
+        $pickup_location    = !empty($holdDetails['pickUpLocation'])
+            ? $holdDetails['pickUpLocation'] : $this->default_location;
+        $level              = isset($holdDetails['level'])
+            && !empty($holdDetails['level']) ? $holdDetails['level'] : "item";
+
+        try {
+            //$needed_before_date = $this->toKohaDate($holdDetails['requiredBy']);
+            $dateObject = \DateTime::createFromFormat("j. n. Y", $holdDetails['requiredBy']);
+            $needed_before_date = $dateObject->format("Y-m-d");
+        } catch (DateException $e) {
+            return array(
+                "success" => false,
+                "sysMessage" => "It seems you entered an invalid expiration date."
+            );
+        }
+
+        if ($this->debug_enabled) {
+            $this->debug("patron: " . $patron);
+            $this->debug("patron_id: " . $patron_id);
+            $this->debug("request_location: " . $request_location);
+            $this->debug("item_id: " . $item_id);
+            $this->debug("bib_id: " . $bib_id);
+            $this->debug("pickup loc: " . $pickup_location);
+            $this->debug("Needed before date: " . $needed_before_date);
+            $this->debug("Level: " . $level);
+        }
+
+
+        if ( $level == "title" ) {
+            $rqString = "HoldTitle&patron_id=$patron_id&bib_id=$bib_id"
+                . "&request_location=$request_location"
+                . "&pickup_location=$pickup_location"
+                . "&pickup_expiry_date=$needed_before_date";
+        } else {
+            $rqString = "HoldItem&patron_id=$patron_id&bib_id=$bib_id"
+                . "&item_id=$item_id"
+                . "&pickup_location=$pickup_location"
+                . "&needed_before_date=$needed_before_date"
+                . "&pickup_expiry_date=$needed_before_date";
+        }
+        
+        $rsp = $this->makeRequest($rqString);
+        
+        //TODO - test this new functionality
+        /*
+        if ( $level == "title" ) {
+        	$rsp2 = $this->makeIlsdiRequest("HoldTitle", 
+        			array("patron_id" => $patron_id,
+        				  "bib_id" => $bib_id,
+        				  "request_location" => $request_location,
+        				  "pickup_location" => $pickup_location,
+        				  "pickup_expiry_date" => $needed_before_date,
+        				  "needed_before_date" => $needed_before_date
+        			));
+        } else {
+        	$rsp2 = $this->makeIlsdiRequest("HoldItem",
+        			array("patron_id" => $patron_id,
+        				  "bib_id" => $bib_id,
+        				  "item_id" => $item_id,
+        				  "pickup_location" => $pickup_location,
+        				  "pickup_expiry_date" => $needed_before_date,
+        				  "needed_before_date" => $needed_before_date
+        			));	
+        }
+        */
+        if ($this->debug_enabled) {
+            $this->debug("Title: " . $rsp->{'title'});
+            $this->debug("Pickup Location: " . $rsp->{'pickup_location'});
+            $this->debug("Code: " . $rsp->{'code'});
+        }
+
+        if ($rsp->{'code'} != "") {
+            return array(
+                "success"    => false,
+                "sysMessage" => $this->getField($rsp->{'code'}) . $holdDetails['level'],
+            );
+        }
+        return array(
+            "success"    => true,
+        	//"sysMessage" => $message,
+        );
+    }
+
+    /**
+     * Get Holding
+     *
+     * This is responsible for retrieving the holding information of a certain
+     * record.
+     *
+     * @param string $id     The record id to retrieve the holdings for
+     * @param array  $patron Patron data
+     *
+     * @throws \VuFind\Exception\Date
+     * @throws ILSException
+     * @return array         On success, an associative array with the following
+     * keys: id, availability (boolean), status, location, reserve, callnumber,
+     * duedate, number, barcode.
+     */
+    public function getHolding($id, array $patron = NULL)
+    {  
+          
+      $this->debug("Function getHolding($id, $patron) called");
+      
+      $started = microtime(TRUE);
+    
+      $holding = array();
+      $available = true;
+      $duedate = $status = '';
+      $loc = $shelf = '';
+      $reserves = "N";
+    
+      $sql = "select i.itemnumber as ITEMNO, i.location as LOCATION, i.holdingbranch as HLDBRNCH, 
+      		i.homebranch as HOMEBRANCH, i.reserves as RESERVES, i.itemcallnumber as CALLNO, i.barcode as BARCODE, 
+      		i.barcode as COPYNO, i.notforloan as NOTFORLOAN, i.itemnotes as PERIONAME, b.frameworkcode as DOCTYPE,
+      		t.frombranch as TRANSFERFROM, t.tobranch as TRANSFERTO
+                    from items i join biblio b on i.biblionumber = b.biblionumber
+                    left outer join (SELECT itemnumber, frombranch, tobranch from branchtransfers where datearrived IS NULL) as t on t.itemnumber = i.itemnumber
+                    where i.biblionumber = :id AND i.itemlost = '0' order by i.itemnumber DESC";
+     $sqlReserves = "select count(*) as RESERVESCOUNT from reserves WHERE biblionumber = :id AND found IS NULL";
+     $sqlWaitingReserve = "select count(*) as WAITING from reserves WHERE itemnumber = :item_id and found = 'W'";
+      //var_dump($this->db);
+      if (!$this->db) {
+        $this->initDB();
+      }
+      try {
+      	$itemSqlStmt = $this->db->prepare($sql);
+      	$itemSqlStmt->execute(array(':id' => $id));
+      	$sqlStmtReserves = $this->db->prepare($sqlReserves);
+      	$sqlStmtWaitingReserve = $this->db->prepare($sqlWaitingReserve);
+      	$sqlStmtReserves->execute(array(':id' => $id));
+      } catch (PDOException $e) {
+        $this->debug('Connection failed: ' . $e->getMessage());
+      }
+      	      
+      if ($this->debug_enabled) {
+        $this->debug("Rows count: " . $itemSqlStmt->rowCount());
+      }
+      
+      $reservesRow = $sqlStmtReserves->fetch();	
+      $reservesCount = $reservesRow["RESERVESCOUNT"];
+      
+      foreach ($itemSqlStmt->fetchAll() as $rowItem) {
+        $inum = $rowItem['ITEMNO'];
+        $sqlStmtWaitingReserve->execute(array(':item_id' => $inum));
+        $waitingReserveRow = $sqlStmtWaitingReserve->fetch();
+        $waitingReserve = $waitingReserveRow["WAITING"];
+        $sql = "select date_due as DUEDATE from issues where itemnumber = :inum";
+        switch ($rowItem['NOTFORLOAN']) {
+          case 0:
+            // If the item is available for loan, then check its current
+            // status
+            $issueSqlStmt = $this->db->prepare($sql);
+            $issueSqlStmt->execute(array(':inum' => $inum));
+            $rowIssue = $issueSqlStmt->fetch();
+            if ($rowIssue) {
+              $available = false;
+              $status = 'Checked out';
+              $duedate = $rowIssue['DUEDATE'];
+            } else {
+              $available = true;
+              $status = 'Available';
+              // No due date for an available item
+              $duedate = '';
+            }
+            break;
+          case 1: // The item is not available for loan
+          default:
+            $available = false;
+            $status = 'Not for loan';
+            $duedate = '';
+            break;
+        }
+        
+        
+        $duedate_formatted = date_format(new \DateTime($duedate), "j. n. Y");
+        
+       
+        //Retrieving the full branch name
+        if ($rowItem['HLDBRNCH'] == null) {
+        	if($rowItem['HOMEBRANCH'] == null) {
+        		$loc = "Unknown";
+        	} else {
+        		$loc = $rowItem['HOMEBRANCH'];
+        	}
+        } else {
+          	$loc = $rowItem['HLDBRNCH'];
+        }
+
+        if($loc != "Unknown") {
+        	$sqlBranch = "select branchname as BNAME from branches where branchcode = :branch";
+        	$branchSqlStmt = $this->db->prepare($sqlBranch);
+        	$branchSqlStmt->execute(array(':branch' => $loc));
+        	$row = $branchSqlStmt->fetch();
+        	if ($row) {
+        		$loc = $row['BNAME'];
+        	}
+        }
+
+        $onTransfer = false;
+        if(($rowItem["TRANSFERFROM"] != null) && ($rowItem["TRANSFERTO"] != null)) {
+        	$branchSqlStmt->execute(array(':branch' => $rowItem["TRANSFERFROM"]));
+        	$rowFrom = $branchSqlStmt->fetch();
+        	$transferfrom = $rowFrom ? $rowFrom["BNAME"] : $rowItem["TRANSFERFROM"];
+        	$branchSqlStmt->execute(array(':branch' => $rowItem["TRANSFERTO"]));
+        	$rowTo = $branchSqlStmt->fetch();
+        	$transferto = $rowTo ? $rowTo["BNAME"] : $rowItem["TRANSFERTO"];
+        	$status = "Na cestÄ› z $transferfrom do $transferto";
+        	$available = false;
+        	$onTransfer = true;
+        }
+        
+        if ($rowItem['DOCTYPE'] == "PE") {  
+        	$rowItem['COPYNO'] = $rowItem['PERIONAME'];  
+        }
+        if ($waitingReserve) {
+        	$available = false;
+        	$status = "Waiting";
+        	$waiting = true;
+        }
+        
+        $holding[] = array(
+            'id'           => $id,
+            'availability' => (string) $available,
+            'item_id'      => $rowItem['ITEMNO'],
+            'status'       => $status,
+            'location'     => $loc,
+        	//'reserve'      => (null == $rowItem['RESERVES']) ? 'N' : $rowItem['RESERVES'],
+        	'reserve'      => 'N',
+            'callnumber'   => ((null == $rowItem['CALLNO']) || ($rowItem['DOCTYPE'] == "PE")) ? '' : $rowItem['CALLNO'],
+            'duedate'      => ($onTransfer || $waiting) ? '' : (string) $duedate_formatted, 
+            'barcode'      => (null == $rowItem['BARCODE']) ? 'Unknown' : $rowItem['BARCODE'],
+            'number'       => (null == $rowItem['COPYNO']) ? '' : $rowItem['COPYNO'],
+        	'requests_placed' => $reservesCount ? $reservesCount : 0,
+        	'frameworkcode'=> $rowItem['DOCTYPE'],
+        );        
+        
+      }
+      
+      //file_put_contents('holding.txt', print_r($holding,TRUE), FILE_APPEND);
+    
+      $this->debug("Processing finished, rows processed: " . count($holding).", took ".(microtime(TRUE)-$started)." sec");    
+      
+      return $holding;
+    }
+    
+    
+    public function getHoldingOld($id, $patron = false)
+    {
+      
+      $holding = array();
+      $available = true;
+      $duedate = $status = '';
+      $loc = $shelf = '';
+      $reserves = "N";
+    
+      $rsp = $this->makeRequest("GetRecords&id=$id");
+    
+      if ($this->debug_enabled) {
+        $this->debug("ISBN: " . $rsp->{'record'}->{'isbn'});
+      }
+    
+      foreach ($rsp->{'record'}->{'items'}->{'item'} as $item) {
+        if ($this->debug_enabled) {
+          $this->debug("Biblio: " . $item->{'biblioitemnumber'});
+          $this->debug("ItemNo: " . $item->{'itemnumber'});
+        }
+        switch ($item->{'notforloan'}) {
+          case 0:
+            if ($item->{'date_due'} != "") {
+              $available = false;
+              $status    = 'Checked out';
+              $duedate   = $this->getField($item->{'date_due'});
+            } else {
+              $available = true;
+              $status    = 'Available';
+              $duedate   = '';
+            }
+            break;
+          case 1: // The item is not available for loan
+          default: $available = false;
+          $status = 'Not for loan';
+          $duedate = '';
+          break;
+        }
+    
+        foreach ($rsp->{'record'}->{'reserves'}->{'reserve'} as $reserve) {
+          if ($reserve->{'suspend'} == '0') {
+            $reserves = "Y";
+            break;
+          }
+        }
+        $holding[] = array(
+            'id'           => (string) $id,
+            'availability' => (string) $available,
+            'item_id'      => $this->getField($item->{'itemnumber'}),
+            'status'       => (string) $status,
+            'location'     => $this->getField($item->{'holdingbranchname'}),
+            'reserve'      => (string) $reserves,
+            'callnumber'   => $this->getField($item->{'itemcallnumber'}),
+            'duedate'      => (string) $duedate,
+            'barcode'      => $this->getField($item->{'barcode'}),
+            'number'       => $this->getField($item->{'copynumber'}),
+        );
+      }
+      return $holding;
+    }    
+    
+    /**
+     * This method queries the ILS for new items
+     * 
+     * @param unknown $page - page number of results to retrieve (counting starts at 1)
+     * @param unknown $limit - the size of each page of results to retrieve
+     * @param unknown $daysOld - the maximum age of records to retrieve in days (maximum 30)
+     * @param string $fundId - optional fund ID to use for limiting results (use a value returned by getFunds, or exclude for no limit); note that “fund” may be a misnomer – if funds are not an appropriate way to limit your new item results, you can return a different set of values from getFunds. The important thing is that this parameter supports an ID returned by getFunds, whatever that may mean.
+     */
+    public function getNewItems($page, $limit, $daysOld, $fundId = null) {
+
+      $this->debug("getNewItems called $page|$limit|$daysOld|$fundId");
+      
+      $items = array();
+      $daysOld = min(abs(intval($daysOld)), 30);
+      $sql = "SELECT distinct biblionumber as id FROM items WHERE itemlost = 0 and stocknumber > 1 and dateaccessioned > DATE_ADD(CURRENT_TIMESTAMP, INTERVAL -$daysOld day) ORDER BY dateaccessioned DESC";      
+            
+      if (!$this->db) {
+        $this->initDB();
+      }
+      
+      $this->debug($sql);
+      
+      $itemSqlStmt = $this->db->prepare($sql);
+      $itemSqlStmt->execute();
+      
+      $rescount = 0;
+      foreach ($itemSqlStmt->fetchAll() as $rowItem) {        
+        $items[] = array (
+            'id' => $rowItem['id']
+        );
+        $rescount++;
+      }
+      
+      $this->debug($rescount." fetched");
+      
+      $results = array_slice($items, ($page - 1) * $limit, ($page * $limit)-1);
+      return array('count' => $rescount, 'results' => $results);      
+    }
+
+    /**
+     * Get Hold Link
+     *
+     * The goal for this method is to return a URL to a "place hold" web page on
+     * the ILS OPAC. This is used for ILSs that do not support an API or method
+     * to place Holds.
+     *
+     * @param string $id      The id of the bib record
+     * @param array  $details Item details from getHoldings return array
+     *
+     * @return string         URL to ILS's OPAC's place hold screen.
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    /*public function getHoldLink($id, $details)
+    {
+        // Web link of the ILS for placing hold on the item
+        return $this->ilsBaseUrl . "/cgi-bin/koha/opac-reserve.pl?biblionumber=$id";
+    }*/
+
+
+
+    /**
+     * Get Patron Fines
+     *
+     * This is responsible for retrieving all fines by a specific patron.
+     *
+     * @param array $patron The patron array from patronLogin
+     *
+     * @throws \VuFind\Exception\Date
+     * @throws ILSException
+     * @return mixed        Array of the patron's fines on success.
+     */
+    public function getMyFines($patron)
+    {
+        $id = 0;
+        $transactionLst = array();
+        $row = $sql = $sqlStmt = '';
+        try {
+            $id = $patron['id'];
+            $sql = "select old_issues.issuedate as DUEDATE, items.biblionumber as " .
+                "BIBNO, items.barcode BARCODE, items.itemnumber as ITEM_ID, items.itemnotes as NOTES " .
+                "from old_issues join items on old_issues.itemnumber = items.itemnumber " .
+                "where old_issues.borrowernumber = :id ORDER BY DUEDATE DESC ";
+ 			if (!$this->db) {
+        		$this->initDB();
+      		}
+            
+			$sqlStmt = $this->db->prepare($sql);
+            $sqlStmt->execute(array(':id' => $id));
+            foreach ($sqlStmt->fetchAll() as $row) {
+                $transactionLst[] = array(
+                    'duedate' => date_format(new \DateTime($row['DUEDATE']), "j. n. Y"),
+                    'id' => $row['BIBNO'],
+                    'barcode' => $row['BARCODE'],
+                    'renew' => $row['RENEWALS'],
+                    'item_id' => $row['ITEM_ID'],
+                    'message' => $row['NOTES'],
+                );
+            }
+            return $transactionLst;
+        }
+        catch (PDOException $e) {
+            throw new ILSException($e->getMessage());
+        }
+    }
+
+
+    /**
+     * Get Patron Fines
+     *
+     * This is responsible for retrieving all fines by a specific patron.
+     *
+     * @param array $patron The patron array from patronLogin
+     *
+     * @throws \VuFind\Exception\Date
+     * @throws ILSException
+     * @return mixed        Array of the patron's fines on success.
+     */
+    public function getMyFines_ILS($patron)
+    {
+        $id = $patron['id'];        
+        $fineLst = array();
+        
+        $rsp = $this->makeRequest(
+            "GetPatronInfo&patron_id=$id" . "&show_contact=0&show_fines=1"
+        );
+
+        if ($this->debug_enabled) {
+            $this->debug("ID: " . $rsp->{'borrowernumber'});
+            $this->debug("Chrgs: " . $rsp->{'charges'});
+        }
+
+        foreach ($rsp->{'fines'}->{'fine'} as $fine) {
+            $fineLst[] = array(
+                'amount'     => 100 * $this->getField($fine->{'amount'}),
+                // FIXME: require accountlines.itemnumber -> issues.issuedate data
+                'checkout'   => "N/A",
+                'fine'       => $this->getField($fine->{'description'}),
+                'balance'    => 100 * $this->getField($fine->{'amountoutstanding'}),
+                'createdate' => $this->getField($fine->{'date'}),
+                // FIXME: require accountlines.itemnumber -> issues.date_due data.
+                'duedate'    => "N/A",
+                // FIXME: require accountlines.itemnumber -> items.biblionumber data 
+                'id'         => "N/A",
+            );
+        }
+        return $fineLst;
+    }
+
+    /**
+     * Get Patron Holds
+     *
+     * This is responsible for retrieving all holds by a specific patron.
+     *
+     * @param array $patron The patron array from patronLogin
+     *
+     * @throws \VuFind\Exception\Date
+     * @throws ILSException
+     * @return array        Array of the patron's holds on success.
+     */
+    public function getMyHolds($patron)
+    {
+        $id = $patron['id'];        
+        $holdLst = array();
+        
+        $rsp = $this->makeRequest(
+            "GetPatronInfo&patron_id=$id" . "&show_contact=0&show_holds=1"
+        );
+
+        if ($this->debug_enabled) {
+            $this->debug("ID: " . $rsp->{'borrowernumber'});
+            //print_r($rsp); // Proof that no itemnumber is returned.
+        }
+        foreach ($rsp->{'holds'}->{'hold'} as $hold) {
+            $holdLst[] = array(
+                'id'       => $this->getField($hold->{'biblionumber'}),
+                'location' => $this->getField($hold->{'branchname'}),
+                // FIXME: require exposure of reserves.expirationdate
+                'expire'   => "N/A",
+                'create'   => date_format(new \DateTime($this->getField($hold->{'reservedate'})), "j. n. Y"),
+                'position' => $this->getField($hold->{'priority'}),
+                'title' => $this->getField($hold->{'title'}),
+                'available' => ($this->getField($hold->{'found'}) == "W")?true:false,
+               	'reserve_id' => $this->getField($hold->{'reserve_id'}),
+            );
+        }
+        return $holdLst;
+    }
+
+    /**
+     * Get Cancel Hold Details
+     *
+     * In order to cancel a hold, Koha requires the patron details and
+     * an item ID. This function returns the item id as a string. This
+     * value is then used by the CancelHolds function.
+     *
+     * @param array $holdDetails An array of item data
+     *
+     * @return string Data for use in a form field
+     */
+    public function getCancelHoldDetails($holdDetails)
+    {
+        return $holdDetails['reserve_id'];
+    }
+
+    /**
+     * Cancel Holds
+     *
+     * Attempts to Cancel a hold or recall on a particular item. The
+     * data in $cancelDetails['details'] is determined by getCancelHoldDetails().
+     *
+     * @param array $cancelDetails An array of item and patron data
+     *
+     * @return array               An array of data on each request including
+     * whether or not it was successful and a system message (if available)
+     */
+    public function cancelHolds($cancelDetails)
+    {
+    	$retVal         = array('count' => 0, 'items' => array());
+    	$details        = $cancelDetails['details'];
+        $patron_id      = $cancelDetails['patron']['id'];
+        $request_prefix = "CancelHold&patron_id=" . $patron_id . "&item_id=";
+
+        foreach ($details as $cancelItem) {
+            $rsp = $this->makeRequest($request_prefix . $cancelItem);
+            if ($rsp->{'code'} != "Canceled") {
+                $retVal['items'][$cancelItem] = array(
+                    'success'    => false,
+                    'status'     => 'hold_cancel_fail',
+                    'sysMessage' => $this->getField($rsp->{'code'}),
+                );
+            } else {
+                $retVal['count']++;
+                $retVal['items'][$cancelItem] = array(
+                    'success' => true,
+                    'status' => 'hold_cancel_success',
+                );
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Get Patron Profile
+     *
+     * This is responsible for retrieving the profile for a specific patron.
+     *
+     * @param array $patron The patron array
+     *
+     * @throws ILSException
+     * @return array        Array of the patron's profile data on success.
+     */
+    public function getMyProfile($patron)
+    {
+        $id = $patron['id'];
+        $profile = array();
+        
+        $rsp = $this->makeRequest(
+            "GetPatronInfo&patron_id=$id" . "&show_contact=1"
+        );
+
+        if ($this->debug_enabled) {
+            $this->debug("Code: " . $rsp->{'code'});
+            $this->debug("Cardnumber: " . $rsp->{'cardnumber'});
+        }
+
+        if ($rsp->{'code'} != 'PatronNotFound') {
+            $profile = array(
+                'firstname' => $this->getField($rsp->{'firstname'}),
+                'lastname'  => $this->getField($rsp->{'surname'}),
+                'address1'  => $this->getField($rsp->{'address'}),
+                'address2'  => $this->getField($rsp->{'address2'}),
+                'zip'       => $this->getField($rsp->{'zipcode'}),
+                'phone'     => $this->getField($rsp->{'phone'}),
+                'group'     => $this->getField($rsp->{'categorycode'}),
+            );
+            return $profile;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get Patron Transactions
+     *
+     * This is responsible for retrieving all transactions (i.e. checked out items)
+     * by a specific patron.
+     *
+     * @param array $patron The patron array from patronLogin
+     *
+     * @throws \VuFind\Exception\Date
+     * @throws ILSException
+     * @return array        Array of the patron's transactions on success.
+     */
+    public function getMyTransactions($patron)
+    {
+    	echo "<!--";
+    	$id = $patron['id'];
+        $transactionLst = array();
+        $start = microtime(true);
+        $rsp = $this->makeRequest(
+            "GetPatronInfo&patron_id=$id" . "&show_contact=0&show_loans=1"
+        );
+        $end = microtime(true);
+        $requestTimes[] = $end - $start;  
+
+        if ($this->debug_enabled) {
+            $this->debug("ID: " . $rsp->{'borrowernumber'});
+        }
+		
+        foreach ($rsp->{'loans'}->{'loan'} as $loan) {
+        	$start = microtime(true);
+        	$rsp2 = $this->makeIlsdiRequest("GetServices", array(
+        			"patron_id" => $id,
+        			"item_id" => $this->getField($loan->{'itemnumber'})
+        			));
+        	$end = microtime(true);
+        	$requestTimes[] = $end - $start;
+        	$renewable = false;
+        	foreach($rsp2->{'AvailableFor'} as $service) {
+        		if ($this->getField($service->{0}) == "loan renewal") {
+        			$renewable = true;
+        		}
+        	}
+
+        	$transactionLst[] = array(
+                'duedate'   => date_format(new \DateTime($this->getField($loan->{'date_due'})), "j. n. Y"),
+                'id'        => $this->getField($loan->{'biblionumber'}),
+                'item_id'   => $this->getField($loan->{'itemnumber'}),
+                'barcode'   => $this->getField($loan->{'barcode'}),
+                'renew'     => $this->getField($loan->{'renewals'}, '0'),
+    			'renewable' => $renewable,
+            );
+        }
+        foreach($requestTimes as $time) {
+        	echo "\n$time\n";
+        }
+        echo "-->";
+        return $transactionLst;
+    }
+    
+
+    /**
+     * Get Renew Details
+     *
+     * In order to renew an item, Koha requires the patron details and
+     * an item id. This function returns the item id as a string which
+     * is then used as submitted form data in checkedOut.php. This
+     * value is then extracted by the RenewMyItems function.
+     *
+     * @param array $checkOutDetails An array of item data
+     *
+     * @return string Data for use in a form field
+     */
+    public function getRenewDetails($checkOutDetails)
+    {
+        return $checkOutDetails['item_id'];
+    }
+
+    /**
+     * Renew My Items
+     *
+     * Function for attempting to renew a patron's items.  The data in
+     * $renewDetails['details'] is determined by getRenewDetails().
+     *
+     * @param array $renewDetails An array of data required for
+     * renewing items including the Patron ID and an array of renewal
+     * IDS
+     *
+     * @return array An array of renewal information keyed by item ID
+     */
+    public function renewMyItems($renewDetails)
+    {
+        $retVal         = array('blocks' => false, 'details' => array());
+        $details        = $renewDetails['details'];
+        $patron_id      = $renewDetails['patron']['id'];
+        $request_prefix = "RenewLoan&patron_id=" . $patron_id . "&item_id=";
+
+        foreach ($details as $renewItem) {
+            $rsp = $this->makeRequest($request_prefix . $renewItem);
+            if ($rsp->{'success'} != '0') {
+                list($date, $time)
+                    = explode(" ", $this->getField($rsp->{'date_due'}));
+                $retVal['details'][$renewItem] = array(
+                    "success"  => true,
+                    "new_date" => $date,
+                    "new_time" => $time,
+                    "item_id"  => $renewItem,
+                );
+            } else {
+                $retVal['details'][$renewItem] = array(
+                    "success"    => false,
+                    "new_date"   => false,
+                    "item_id"    => $renewItem,
+                    //"sysMessage" => $this->getField($rsp->{'error'}),
+                );
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Get Purchase History
+     *
+     * This is responsible for retrieving the acquisitions history data for the
+     * specific record (usually recently received issues of a serial).
+     *
+     * @param string $id The record id to retrieve the info for
+     *
+     * @throws ILSException
+     * @return array     An array with the acquisitions data on success.
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function getPurchaseHistory($id)
+    {
+        // TODO
+        return array();
+    }
+
+    /**
+     * Get Status
+     *
+     * This is responsible for retrieving the status information of a certain
+     * record.
+     *
+     * @param string $id The record id to retrieve the holdings for
+     *
+     * @throws ILSException
+     * @return mixed     On success, an associative array with the following keys:
+     * id, availability (boolean), status, location, reserve, callnumber.
+     */
+    public function getStatus($id)
+    {
+        return $this->getHolding($id);
+    }
+
+    /**
+     * Get Statuses
+     *
+     * This is responsible for retrieving the status information for a
+     * collection of records.
+     *
+     * @param array $idLst The array of record ids to retrieve the status for
+     *
+     * @throws ILSException
+     * @return array       An array of getStatus() return values on success.
+     */
+    public function getStatuses($idLst)
+    {
+        $this->debug("IDs:".implode(',', $idLst));
+        
+        $statusLst = array();
+        foreach ($idLst as $id) {
+            $statusLst[] = $this->getStatus($id);
+        }
+        return $statusLst;
+    }
+
+    /**
+     * Get suppressed records.
+     *
+    * @throws ILSException
+     * @return array ID numbers of suppressed records in the system.
+     */
+    public function getSuppressedRecords()
+    {
+      try {
+ 			  if (!$this->db) {
+          $this->initDB();
+      	}
+        $sqlStmt = $this->db->prepare($sql);
+        $result = $sqlStmt->fetchAll(PDO::FETCH_COLUMN, 0);
+      } catch (PDOException $e) {
+        throw new ILSException($e->getMessage());
+      }
+      return $result;
+    }
+
+    /**
+     * Patron Login
+     *
+     * This is responsible for authenticating a patron against the catalog.
+     *
+     * @param string $username The patron username
+     * @param string $password The patron's password
+     *
+     * @throws ILSException
+     * @return mixed          Associative array of patron info on successful login,
+     * null on unsuccessful login.
+     */
+    public function patronLogin($username, $password)
+    {
+        $patron = array();
+
+        $idObj = $this->makeRequest(
+            "AuthenticatePatron" . "&username=" . $username
+            . "&password=" . $password
+        );
+        if ($this->debug_enabled) {
+            $this->debug("Code: " . $idObj->{'code'});
+            $this->debug("ID: " . $idObj->{'id'});
+        }
+        $id = $this->getField($idObj->{'id'},0);
+		if($id) {
+            $rsp = $this->makeRequest(
+                "GetPatronInfo&patron_id=$id&show_contact=1"
+            );
+            $profile = array(
+            	'id'           => $this->getField($idObj->{'id'}),
+                'firstname'    => $this->getField($rsp->{'firstname'}),
+                'lastname'     => $this->getField($rsp->{'lastname'}),
+                'cat_username' => $username,
+                'cat_password' => $password,
+                'email'        => $this->getField($rsp->{'email'}),
+                'major'        => null,
+                'college'      => null,
+            );
+            return $profile;
+        } else {
+            return null;
+        }
+    }
+} 
-- 
GitLab