From c0159f4b796e690af643ac99b54b3b974f5dc26e Mon Sep 17 00:00:00 2001 From: Gilles Crettenand Date: Tue, 2 Jun 2015 17:26:01 +0200 Subject: [PATCH] change line endings --- .gitignore | 8 +- Lib/Configuration.php | 212 ++-- Lib/Exception/BookNotFoundException.php | 18 +- Lib/Exception/InvalidAttributeException.php | 16 +- Lib/Exception/SqlException.php | 38 +- Lib/Exception/WebException.php | 48 +- Lib/Search/BookSearch.php | 178 +-- Lib/WebService.php | 244 ++-- Lib/db/AudioBook.php | 500 ++++----- Lib/db/Connection.php | 372 +++--- Lib/db/DbMapping.php | 252 ++--- Lib/db/User.php | 416 +++---- NetBiblio.php | 1122 +++++++++---------- README.md | 8 +- index.php | 40 +- mobile.php | 6 +- 16 files changed, 1739 insertions(+), 1739 deletions(-) diff --git a/.gitignore b/.gitignore index 5444415..2f0e4e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.idea -logs/ -*configuration.local.php -*~ +.idea +logs/ +*configuration.local.php +*~ diff --git a/Lib/Configuration.php b/Lib/Configuration.php index 75dd2b8..34d8cb1 100644 --- a/Lib/Configuration.php +++ b/Lib/Configuration.php @@ -1,106 +1,106 @@ - array( - 'driver' => 'SQL Server Native Client 11.0', - 'server' => 'BSR2012\SQLEXPRESS', - 'port' => '1433', - 'username' => 'alcoda', - 'password' => 'alcodaonly', - 'name' => 'NetBiblio3', - ), - 'solr' => array( - 'server' => '192.168.1.250', - 'port' => '8983', - 'username' => '', - 'password' => '', - 'path' => 'solr/bsr1', - 'result_count' => 10, - ), - 'log' => array( - 'file' => 'C:\inetpub\wwwroot\WebService\logs\log.txt', - // The greater the verbosity, the more is displayed - // 0 : no log at all - // 1 : log function calls and errors - // 2 : log parameters and sent data - 'verbosity' => 0, - ), - 'session' => array( - 'save_path' => '' - ), - - 'checkfile_url' => 'http://fichiers.bibliothequesonore.ch/checkfile.php?', - - 'netbiblio_worker_id' => 45, - 'www_employee_id' => 45, - 'www_library_id' => 2, - ); - - private $custom_config = 'configuration.local.php'; - - private function __construct() { - // by default, set the session save path to the default value; - $this->values['session']['save_path'] = session_save_path(); - - if(file_exists($this->custom_config)) { - require_once($this->custom_config); - - if(! isset($configuration) || ! is_array($configuration)) { - throw new \RuntimeException("You custom configuration in '{$this->custom_config}' must be in a variable named '\$configuration' and be an array."); - } - - $this->values = array_replace_recursive($this->values, $configuration); - } - } - - private function dotNotationAccess($data, $key, $default=null) - { - $keys = explode('.', $key); - foreach ($keys as $k) { - if (!is_array($data)) { - throw new \Exception("Try to access non-array as array, key '$key''"); - } - if (!isset($data[$k])) { - return $default; - } - - $data = $data[$k]; - } - return $data; - } - - private function value($name, $default) { - return $this->dotNotationAccess($this->values, $name, $default); - } - - /** - * @param $name - * @param mixed $default the default value for your configuration option - * @return mixed return the configuration value if the key is find, the default otherwise - */ - public static function get($name, $default = null) { - if(is_null(self::$instance)) { - self::$instance = new Configuration(); - } - - return self::$instance->value($name, $default); - } -} + array( + 'driver' => 'SQL Server Native Client 11.0', + 'server' => 'BSR2012\SQLEXPRESS', + 'port' => '1433', + 'username' => 'alcoda', + 'password' => 'alcodaonly', + 'name' => 'NetBiblio3', + ), + 'solr' => array( + 'server' => '192.168.1.250', + 'port' => '8983', + 'username' => '', + 'password' => '', + 'path' => 'solr/bsr1', + 'result_count' => 10, + ), + 'log' => array( + 'file' => 'C:\inetpub\wwwroot\WebService\logs\log.txt', + // The greater the verbosity, the more is displayed + // 0 : no log at all + // 1 : log function calls and errors + // 2 : log parameters and sent data + 'verbosity' => 0, + ), + 'session' => array( + 'save_path' => '' + ), + + 'checkfile_url' => 'http://fichiers.bibliothequesonore.ch/checkfile.php?', + + 'netbiblio_worker_id' => 45, + 'www_employee_id' => 45, + 'www_library_id' => 2, + ); + + private $custom_config = 'configuration.local.php'; + + private function __construct() { + // by default, set the session save path to the default value; + $this->values['session']['save_path'] = session_save_path(); + + if(file_exists($this->custom_config)) { + require_once($this->custom_config); + + if(! isset($configuration) || ! is_array($configuration)) { + throw new \RuntimeException("You custom configuration in '{$this->custom_config}' must be in a variable named '\$configuration' and be an array."); + } + + $this->values = array_replace_recursive($this->values, $configuration); + } + } + + private function dotNotationAccess($data, $key, $default=null) + { + $keys = explode('.', $key); + foreach ($keys as $k) { + if (!is_array($data)) { + throw new \Exception("Try to access non-array as array, key '$key''"); + } + if (!isset($data[$k])) { + return $default; + } + + $data = $data[$k]; + } + return $data; + } + + private function value($name, $default) { + return $this->dotNotationAccess($this->values, $name, $default); + } + + /** + * @param $name + * @param mixed $default the default value for your configuration option + * @return mixed return the configuration value if the key is find, the default otherwise + */ + public static function get($name, $default = null) { + if(is_null(self::$instance)) { + self::$instance = new Configuration(); + } + + return self::$instance->value($name, $default); + } +} diff --git a/Lib/Exception/BookNotFoundException.php b/Lib/Exception/BookNotFoundException.php index d647580..f8eec20 100644 --- a/Lib/Exception/BookNotFoundException.php +++ b/Lib/Exception/BookNotFoundException.php @@ -1,9 +1,9 @@ -query = $query; - parent::__construct($message, 0); - } - - public function getSqlError() - { - return $this->getMessage().' while executing: '.$this->query; - } -} +query = $query; + parent::__construct($message, 0); + } + + public function getSqlError() + { + return $this->getMessage().' while executing: '.$this->query; + } +} diff --git a/Lib/Exception/WebException.php b/Lib/Exception/WebException.php index aec0f4b..7476e72 100644 --- a/Lib/Exception/WebException.php +++ b/Lib/Exception/WebException.php @@ -1,24 +1,24 @@ -excname = $name; - parent::__construct($reason, $code); - } - - public function getName() - { - return $this->excname; - } -} +excname = $name; + parent::__construct($reason, $code); + } + + public function getName() + { + return $this->excname; + } +} diff --git a/Lib/Search/BookSearch.php b/Lib/Search/BookSearch.php index 1507a52..e678ca9 100644 --- a/Lib/Search/BookSearch.php +++ b/Lib/Search/BookSearch.php @@ -1,89 +1,89 @@ - Configuration::get('solr.server'), - 'port' => Configuration::get('solr.port'), - 'login' => Configuration::get('solr.username'), - 'password' => Configuration::get('solr.password'), - 'path' => Configuration::get('solr.path'), - ); - - $this->client = new \SolrClient($options); - $this->query = new \SolrQuery(); - $this->query->setQuery('*:*'); - - $this->query->addField('*'); - - $this->query->addParam('q.op', 'AND'); - } - - public function addQuery($queryText, $queryField = null, $escape = true) - { - if($escape) { - $queryText = \SolrUtils::escapeQueryChars($queryText); - } - - if (strlen($queryField) > 0) { - $queryText = "$queryField:\"$queryText\""; - } - - $this->queryParts[] = $queryText; - } - - public function addSortField($field, $order = \SolrQuery::ORDER_DESC) - { - $this->query->addSortField($field, $order); - } - - /** - * @param int $start - * @param int $count - * @return array - * @throws WebException - */ - public function getResults($start = 0, $count = 15) - { - if (count($this->queryParts) == 0) - $query = '*:*'; - else { - $query = implode(' AND ', $this->queryParts); - } - $this->query->setQuery($query); - $this->query->setStart($start); - $this->query->setRows($count); - - try { - $results = $this->client->query($this->query)->getResponse(); - } catch(\SolrClientException $e) { - throw new WebException ("SolrError", $e->getMessage(), -700); - } - - $books = isset($results['response']['docs']) && $results['response']['docs'] ? - array_map(function($o) { return (array) $o; }, $results['response']['docs']) : - array(); - - return array( - 'count' => $results['response']['numFound'], - 'facets' => $results['facet_counts']['facet_fields'], - 'books' => $books, - ); - } -} + Configuration::get('solr.server'), + 'port' => Configuration::get('solr.port'), + 'login' => Configuration::get('solr.username'), + 'password' => Configuration::get('solr.password'), + 'path' => Configuration::get('solr.path'), + ); + + $this->client = new \SolrClient($options); + $this->query = new \SolrQuery(); + $this->query->setQuery('*:*'); + + $this->query->addField('*'); + + $this->query->addParam('q.op', 'AND'); + } + + public function addQuery($queryText, $queryField = null, $escape = true) + { + if($escape) { + $queryText = \SolrUtils::escapeQueryChars($queryText); + } + + if (strlen($queryField) > 0) { + $queryText = "$queryField:\"$queryText\""; + } + + $this->queryParts[] = $queryText; + } + + public function addSortField($field, $order = \SolrQuery::ORDER_DESC) + { + $this->query->addSortField($field, $order); + } + + /** + * @param int $start + * @param int $count + * @return array + * @throws WebException + */ + public function getResults($start = 0, $count = 15) + { + if (count($this->queryParts) == 0) + $query = '*:*'; + else { + $query = implode(' AND ', $this->queryParts); + } + $this->query->setQuery($query); + $this->query->setStart($start); + $this->query->setRows($count); + + try { + $results = $this->client->query($this->query)->getResponse(); + } catch(\SolrClientException $e) { + throw new WebException ("SolrError", $e->getMessage(), -700); + } + + $books = isset($results['response']['docs']) && $results['response']['docs'] ? + array_map(function($o) { return (array) $o; }, $results['response']['docs']) : + array(); + + return array( + 'count' => $results['response']['numFound'], + 'facets' => $results['facet_counts']['facet_fields'], + 'books' => $books, + ); + } +} diff --git a/Lib/WebService.php b/Lib/WebService.php index 4a5ea78..0aed98a 100644 --- a/Lib/WebService.php +++ b/Lib/WebService.php @@ -1,122 +1,122 @@ -log .= $message."\n"; - } - - public function Run() - { - $this->log("------------------"); - $this->log("Start request", 1, true); - $data = array(); - - try { - $result = $this->Call(); - $data["result"][$this->func] = $result; - } catch (WebException $e) { - $data["error"]["code"] = $e->getCode(); - $data["error"]["name"] = $e->getName(); - $data["error"]["reason"] = $e->getMessage(); - $this->status = 400; - - $this->log(sprintf("Error : [%s] %s", $e->getCode(), $e->getName())); - } catch (\Exception $e) { - $data["failure"]["message"] = $e->getMessage(); - $this->status = 500; - - $this->log(sprintf("Failure : %s", $e->getMessage())); - } - - $this->Send($data); - - $this->log("Request finished", 1, true); - $this->log("------------------\n\n"); - - if(Configuration::get('log.verbosity') > 0) { - file_put_contents(Configuration::get('log.file'), $this->log, FILE_APPEND | LOCK_EX); - } - } - - private function Call() - { - ob_start(); - session_save_path(Configuration::get('session.save_path')); - session_start(); - - $params = empty($_GET) ? $_POST : $_GET; - if (empty($params)) { - throw new WebException ("CallArgument", "arguments error", -1); - } - - if (!array_key_exists("func", $params)) { - throw new WebException ("CallArgFunction", "no 'func' specified", -2); - } - - $this->func = $params["func"]; - unset($params['func']); - - if (!is_callable(array($this, $this->func))) { - throw new WebException ("CallFunction", "'func' method not available", -3); - } - - $rm = new \ReflectionMethod($this, $this->func); - $nbParams = count($params); - $nbArgsFix = $rm->getNumberOfRequiredParameters(); - $nbArgs = $rm->getNumberOfParameters(); - - /* Check the number of arguments. */ - if ($nbParams < $nbArgsFix) { - throw new WebException ("CallArgNumber", "you must provide at least " . $nbArgsFix . " arguments", 4); - } - if ($nbParams > $nbArgs) { - throw new WebException ("CallArgNumber", "you must provide at most " . $nbArgs . " arguments", 4); - } - - $this->log("Calling '".$this->func."'"); - $this->log("Params: ".print_r($params, true), 2); - return call_user_func_array(array($this, $this->func), $params); - } - - private function Send(array $data) - { - static $status_messages = array( - 200 => 'Ok', - 400 => 'Bad request', - 404 => 'Not Found', - 403 => 'Not Authorized', - 500 => 'Server Error', - ); - - header(sprintf('HTTP/1.0 %s %s', $this->status, $status_messages[$this->status])); - - ob_clean(); - flush(); - - $this->log("Data: ".print_r($data, true), 2); - echo json_encode($data); - } -} +log .= $message."\n"; + } + + public function Run() + { + $this->log("------------------"); + $this->log("Start request", 1, true); + $data = array(); + + try { + $result = $this->Call(); + $data["result"][$this->func] = $result; + } catch (WebException $e) { + $data["error"]["code"] = $e->getCode(); + $data["error"]["name"] = $e->getName(); + $data["error"]["reason"] = $e->getMessage(); + $this->status = 400; + + $this->log(sprintf("Error : [%s] %s", $e->getCode(), $e->getName())); + } catch (\Exception $e) { + $data["failure"]["message"] = $e->getMessage(); + $this->status = 500; + + $this->log(sprintf("Failure : %s", $e->getMessage())); + } + + $this->Send($data); + + $this->log("Request finished", 1, true); + $this->log("------------------\n\n"); + + if(Configuration::get('log.verbosity') > 0) { + file_put_contents(Configuration::get('log.file'), $this->log, FILE_APPEND | LOCK_EX); + } + } + + private function Call() + { + ob_start(); + session_save_path(Configuration::get('session.save_path')); + session_start(); + + $params = empty($_GET) ? $_POST : $_GET; + if (empty($params)) { + throw new WebException ("CallArgument", "arguments error", -1); + } + + if (!array_key_exists("func", $params)) { + throw new WebException ("CallArgFunction", "no 'func' specified", -2); + } + + $this->func = $params["func"]; + unset($params['func']); + + if (!is_callable(array($this, $this->func))) { + throw new WebException ("CallFunction", "'func' method not available", -3); + } + + $rm = new \ReflectionMethod($this, $this->func); + $nbParams = count($params); + $nbArgsFix = $rm->getNumberOfRequiredParameters(); + $nbArgs = $rm->getNumberOfParameters(); + + /* Check the number of arguments. */ + if ($nbParams < $nbArgsFix) { + throw new WebException ("CallArgNumber", "you must provide at least " . $nbArgsFix . " arguments", 4); + } + if ($nbParams > $nbArgs) { + throw new WebException ("CallArgNumber", "you must provide at most " . $nbArgs . " arguments", 4); + } + + $this->log("Calling '".$this->func."'"); + $this->log("Params: ".print_r($params, true), 2); + return call_user_func_array(array($this, $this->func), $params); + } + + private function Send(array $data) + { + static $status_messages = array( + 200 => 'Ok', + 400 => 'Bad request', + 404 => 'Not Found', + 403 => 'Not Authorized', + 500 => 'Server Error', + ); + + header(sprintf('HTTP/1.0 %s %s', $this->status, $status_messages[$this->status])); + + ob_clean(); + flush(); + + $this->log("Data: ".print_r($data, true), 2); + echo json_encode($data); + } +} diff --git a/Lib/db/AudioBook.php b/Lib/db/AudioBook.php index 2f8d4ca..2b8fec4 100644 --- a/Lib/db/AudioBook.php +++ b/Lib/db/AudioBook.php @@ -1,251 +1,251 @@ -next()) { - $books[$row['id']] = $raw ? $row : new AudioBook($row); - } - - return $multiple ? $books : reset($books); - } - - /** - * Retrieve the list of all readers (volunteers) having read at least 4 books (2 notices per book). - * Returns an associative array containing $lastname and $firstname - */ - public static function listOfReaders() - { - $sql = "SELECT - count(*), - ContentShortPart AS name - FROM NoticeFields - WHERE Tag=901 - GROUP BY ContentShortPart - HAVING count(*) > 6 - ORDER BY SUBSTRING(ContentShortPart, CHARINDEX(' ', ContentShortPart)+1, 15);"; - - $results = Connection::execute($sql); - return array_map(function($row) { - $fullname = str_replace("*", "", $row['name']); - $parts = explode(" ", $fullname); - $firstname = array_shift($parts); - $lastname = implode(" ", $parts); - return array( - 'lastname' => $lastname, - 'firstname' => $firstname); - }, $results->to_array()); - } - - /** - * Retrieve the list of all type available in the database. - * @param boolean $withJeunesse add 'Jeunesse' to the list - * @return array - */ - public static function ListOfGenres($withJeunesse = false) - { - $sql = "SELECT DISTINCT - LTRIM(RTRIM(Codes.Code)) as code, - LTRIM(RTRIM(Codes.TextFre)) AS text - FROM Codes - INNER JOIN Notices ON Codes.Code = Notices.MediaType2Code - WHERE - Codes.Type = 2 - AND Notices.NoticeNr NOT LIKE '%~%' - AND Notices.NoticeNr NOT LIKE '%V%' - AND Notices.NoticeNr NOT LIKE '%T%' - AND Notices.MediaType1Code = 'CDD';"; - - $results = Connection::execute($sql)->to_array(); - - if($withJeunesse) { - array_unshift($results, array('code' => 'J', 'text' => 'Jeunesse')); - } - - return $results; - } - - /** - * Retrieve the list of all books currently lended to readers. - */ - public static function inReading() - { - $sql = "SELECT - noticenr, title, author, displayName - FROM notices, items, circulations, useraccounts - WHERE - mediatype1code='N' and NoticeNr not like '%~%' - AND items.noticeid = notices.noticeid - AND items.ItemID=circulations.ItemID - AND useraccounts.useraccountid=circulations.useraccountid - ORDER BY author, title;"; - - $results = Connection::execute($sql); - return array_map(function($row) { - return array( - "noticenr" => $row['noticenr'], - "auteur" => $row['author'], - "titre" => $row['title'], - "lecteur" => $row['displayName'] - ); - }, $results->to_array()); - } - - public function __set($name, $value) - { - if ($name == 'code' && is_string($value)) { - $value = preg_replace('/[~a-zA-Z]/', '', $value); - } - parent::__set($name, $value); - } +next()) { + $books[$row['id']] = $raw ? $row : new AudioBook($row); + } + + return $multiple ? $books : reset($books); + } + + /** + * Retrieve the list of all readers (volunteers) having read at least 4 books (2 notices per book). + * Returns an associative array containing $lastname and $firstname + */ + public static function listOfReaders() + { + $sql = "SELECT + count(*), + ContentShortPart AS name + FROM NoticeFields + WHERE Tag=901 + GROUP BY ContentShortPart + HAVING count(*) > 6 + ORDER BY SUBSTRING(ContentShortPart, CHARINDEX(' ', ContentShortPart)+1, 15);"; + + $results = Connection::execute($sql); + return array_map(function($row) { + $fullname = str_replace("*", "", $row['name']); + $parts = explode(" ", $fullname); + $firstname = array_shift($parts); + $lastname = implode(" ", $parts); + return array( + 'lastname' => $lastname, + 'firstname' => $firstname); + }, $results->to_array()); + } + + /** + * Retrieve the list of all type available in the database. + * @param boolean $withJeunesse add 'Jeunesse' to the list + * @return array + */ + public static function ListOfGenres($withJeunesse = false) + { + $sql = "SELECT DISTINCT + LTRIM(RTRIM(Codes.Code)) as code, + LTRIM(RTRIM(Codes.TextFre)) AS text + FROM Codes + INNER JOIN Notices ON Codes.Code = Notices.MediaType2Code + WHERE + Codes.Type = 2 + AND Notices.NoticeNr NOT LIKE '%~%' + AND Notices.NoticeNr NOT LIKE '%V%' + AND Notices.NoticeNr NOT LIKE '%T%' + AND Notices.MediaType1Code = 'CDD';"; + + $results = Connection::execute($sql)->to_array(); + + if($withJeunesse) { + array_unshift($results, array('code' => 'J', 'text' => 'Jeunesse')); + } + + return $results; + } + + /** + * Retrieve the list of all books currently lended to readers. + */ + public static function inReading() + { + $sql = "SELECT + noticenr, title, author, displayName + FROM notices, items, circulations, useraccounts + WHERE + mediatype1code='N' and NoticeNr not like '%~%' + AND items.noticeid = notices.noticeid + AND items.ItemID=circulations.ItemID + AND useraccounts.useraccountid=circulations.useraccountid + ORDER BY author, title;"; + + $results = Connection::execute($sql); + return array_map(function($row) { + return array( + "noticenr" => $row['noticenr'], + "auteur" => $row['author'], + "titre" => $row['title'], + "lecteur" => $row['displayName'] + ); + }, $results->to_array()); + } + + public function __set($name, $value) + { + if ($name == 'code' && is_string($value)) { + $value = preg_replace('/[~a-zA-Z]/', '', $value); + } + parent::__set($name, $value); + } } \ No newline at end of file diff --git a/Lib/db/Connection.php b/Lib/db/Connection.php index 5e2dd56..8627761 100644 --- a/Lib/db/Connection.php +++ b/Lib/db/Connection.php @@ -1,187 +1,187 @@ -is_error()) { - if($throw_error) { - throw new SqlException($result->get_error(), $query); - } - - return $result->get_error(); - } - - return $result; - } - - public static function get() - { - if (is_null(self::$db)) { - $dsn = sprintf( - "Driver={%s};Server=%s,%s;Database=%s;", - Configuration::get('db.driver'), - Configuration::get('db.server'), - Configuration::get('db.port'), - Configuration::get('db.name') - ); - self::$db = odbc_pconnect($dsn, Configuration::get('db.username'), Configuration::get('db.password')); - - if (self::$db === false) { - throw new SqlException("Unable to connect to the server."); - } - } - // Return the connection - return self::$db; - } - - final private function __clone() {} -} - -class OdbcResultSet implements \Iterator, \ArrayAccess -{ - public $length; - - private $results; - private $error; - private $num_fields; - private $cursor_index; - - public function __construct($odbc_result) - { - if ($odbc_result === false) { - $this->error = odbc_errormsg(Connection::get()); - } else { - try { - $this->results = array(); - $this->num_fields = odbc_num_fields($odbc_result); - - if ($this->num_fields > 0) { - while ($row = odbc_fetch_row($odbc_result)) { - $data = array(); - for ($i = 1; $i <= $this->num_fields; ++$i) { - $data[odbc_field_name($odbc_result, $i)] = utf8_encode(odbc_result($odbc_result, $i)); - } - $this->results[] = $data; - } - }; - } catch (\Exception $e) { - print($e->getMessage()); - } - - $this->cursor_index = 0; - $this->length = count($this->results); - odbc_free_result($odbc_result); - } - } - - public function is_error() - { - return ($this->error ? true : false); - } - - public function get_error() - { - return $this->error; - } - - public function get_row() - { - return $this->current(); - } - - public function to_array() - { - return $this->results; - } - - // ArrayAccess - /** - * @param int $offset - * @return bool - */ - public function offsetExists($offset) - { - return !$this->error && $this->cursor_index < $this->length && $this->cursor_index >= 0; - } - - /** - * @param int $offset - * @return bool|array - */ - public function offsetGet($offset) - { - return $this->offsetExists($offset) ? $this->results[$offset] : false; - } - - public function offsetSet($offset, $value) - { - if($this->offsetExists($offset)) { - $this->results[$offset] = $value; - } - } - - public function offsetUnset($offset) - { - throw new \RuntimeException("This makes no sense at all."); - } - - // Iterator - /** - * @return bool|array - */ - public function current() - { - return $this->offsetGet($this->cursor_index); - } - - /** - * @return int - */ - public function key() - { - return $this->cursor_index; - } - - /** - * @return array|bool - */ - public function next() - { - $current = $this->current(); - ++$this->cursor_index; - return $current; - } - - public function rewind() - { - $this->cursor_index = 0; - } - - /** - * @return bool - */ - public function valid() - { - return $this->offsetExists($this->cursor_index); - } +is_error()) { + if($throw_error) { + throw new SqlException($result->get_error(), $query); + } + + return $result->get_error(); + } + + return $result; + } + + public static function get() + { + if (is_null(self::$db)) { + $dsn = sprintf( + "Driver={%s};Server=%s,%s;Database=%s;", + Configuration::get('db.driver'), + Configuration::get('db.server'), + Configuration::get('db.port'), + Configuration::get('db.name') + ); + self::$db = odbc_pconnect($dsn, Configuration::get('db.username'), Configuration::get('db.password')); + + if (self::$db === false) { + throw new SqlException("Unable to connect to the server."); + } + } + // Return the connection + return self::$db; + } + + final private function __clone() {} +} + +class OdbcResultSet implements \Iterator, \ArrayAccess +{ + public $length; + + private $results; + private $error; + private $num_fields; + private $cursor_index; + + public function __construct($odbc_result) + { + if ($odbc_result === false) { + $this->error = odbc_errormsg(Connection::get()); + } else { + try { + $this->results = array(); + $this->num_fields = odbc_num_fields($odbc_result); + + if ($this->num_fields > 0) { + while ($row = odbc_fetch_row($odbc_result)) { + $data = array(); + for ($i = 1; $i <= $this->num_fields; ++$i) { + $data[odbc_field_name($odbc_result, $i)] = utf8_encode(odbc_result($odbc_result, $i)); + } + $this->results[] = $data; + } + }; + } catch (\Exception $e) { + print($e->getMessage()); + } + + $this->cursor_index = 0; + $this->length = count($this->results); + odbc_free_result($odbc_result); + } + } + + public function is_error() + { + return ($this->error ? true : false); + } + + public function get_error() + { + return $this->error; + } + + public function get_row() + { + return $this->current(); + } + + public function to_array() + { + return $this->results; + } + + // ArrayAccess + /** + * @param int $offset + * @return bool + */ + public function offsetExists($offset) + { + return !$this->error && $this->cursor_index < $this->length && $this->cursor_index >= 0; + } + + /** + * @param int $offset + * @return bool|array + */ + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->results[$offset] : false; + } + + public function offsetSet($offset, $value) + { + if($this->offsetExists($offset)) { + $this->results[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + throw new \RuntimeException("This makes no sense at all."); + } + + // Iterator + /** + * @return bool|array + */ + public function current() + { + return $this->offsetGet($this->cursor_index); + } + + /** + * @return int + */ + public function key() + { + return $this->cursor_index; + } + + /** + * @return array|bool + */ + public function next() + { + $current = $this->current(); + ++$this->cursor_index; + return $current; + } + + public function rewind() + { + $this->cursor_index = 0; + } + + /** + * @return bool + */ + public function valid() + { + return $this->offsetExists($this->cursor_index); + } } \ No newline at end of file diff --git a/Lib/db/DbMapping.php b/Lib/db/DbMapping.php index 219abc8..d7fb8e9 100644 --- a/Lib/db/DbMapping.php +++ b/Lib/db/DbMapping.php @@ -1,126 +1,126 @@ -setAttributes($attributes); - } - - /** - * Define a bunch of attribute given by an associative array - * @param array $attributes - */ - public function setAttributes(array $attributes) - { - $this->assertAttributes($attributes); - foreach ($attributes as $key => $value) { - $this->__set($key, $value); - } - } - - /** - * Ensure that all keys from attributes are authorized - * @param array $attributes - */ - private function assertAttributes(array $attributes) - { - foreach ($attributes as $key => $value) { - $this->assertAttribute($key); - } - } - - /** - * Ensure that name attribute is authorized - * If public_only is false, check agains PRIVATE_ATTRIBUTES_NAME too. - * Those one cannot be accessed via setAttributes and other batch methods. - * @param string $name - * @param bool $public_only - * @throws InvalidAttributeException if the attribute is not a valid one - */ - private function assertAttribute($name, $public_only = TRUE) - { - if (strpos($this->attributeNames, $name) === false && ($public_only || strpos($this->privateAttributeNames, $name) === false)) { - throw(new InvalidAttributeException("The attribute $name is invalid")); - } - } - - /** - * Get a user attribute or the linked whishes - - * @param string $name - * @return mixed - */ - public function __get($name) - { - $sql_safe = FALSE; - if (strpos($name, 'sql_') === 0) { - $name = substr($name, 4); - $sql_safe = TRUE; - } - $this->assertAttribute($name, false); - if (isset($this->attributes[$name])) { - $value = $this->attributes[$name]; - if ($sql_safe) { - $value = str_replace("'", "''", $value); - } - return $value; - } else { - return NULL; - } - } - - public function to_array() { - return $this->attributes; - } - - /** - * Set a user attribute - * @param string $name - * @param mixed $value - */ - public function __set($name, $value) - { - $this->assertAttribute($name, false); - $this->attributes[$name] = $value; - } - - /** - * Function to retrieve data from an id. - * @param int $id - * @return DbMapping - */ - public static function find($id) { - throw new \RuntimeException("This method must be implemented in child classes."); - } - - /** - * Return all the public attributes in an array; - */ - public function toArray() - { - $result = array(); - foreach ($this->attributes as $name => $value) { - if (strpos($this->attributeNames, $name) !== false) { - $result[$name] = $value; - } - } - return $result; - } -} +setAttributes($attributes); + } + + /** + * Define a bunch of attribute given by an associative array + * @param array $attributes + */ + public function setAttributes(array $attributes) + { + $this->assertAttributes($attributes); + foreach ($attributes as $key => $value) { + $this->__set($key, $value); + } + } + + /** + * Ensure that all keys from attributes are authorized + * @param array $attributes + */ + private function assertAttributes(array $attributes) + { + foreach ($attributes as $key => $value) { + $this->assertAttribute($key); + } + } + + /** + * Ensure that name attribute is authorized + * If public_only is false, check agains PRIVATE_ATTRIBUTES_NAME too. + * Those one cannot be accessed via setAttributes and other batch methods. + * @param string $name + * @param bool $public_only + * @throws InvalidAttributeException if the attribute is not a valid one + */ + private function assertAttribute($name, $public_only = TRUE) + { + if (strpos($this->attributeNames, $name) === false && ($public_only || strpos($this->privateAttributeNames, $name) === false)) { + throw(new InvalidAttributeException("The attribute $name is invalid")); + } + } + + /** + * Get a user attribute or the linked whishes + + * @param string $name + * @return mixed + */ + public function __get($name) + { + $sql_safe = FALSE; + if (strpos($name, 'sql_') === 0) { + $name = substr($name, 4); + $sql_safe = TRUE; + } + $this->assertAttribute($name, false); + if (isset($this->attributes[$name])) { + $value = $this->attributes[$name]; + if ($sql_safe) { + $value = str_replace("'", "''", $value); + } + return $value; + } else { + return NULL; + } + } + + public function to_array() { + return $this->attributes; + } + + /** + * Set a user attribute + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->assertAttribute($name, false); + $this->attributes[$name] = $value; + } + + /** + * Function to retrieve data from an id. + * @param int $id + * @return DbMapping + */ + public static function find($id) { + throw new \RuntimeException("This method must be implemented in child classes."); + } + + /** + * Return all the public attributes in an array; + */ + public function toArray() + { + $result = array(); + foreach ($this->attributes as $name => $value) { + if (strpos($this->attributeNames, $name) !== false) { + $result[$name] = $value; + } + } + return $result; + } +} diff --git a/Lib/db/User.php b/Lib/db/User.php index 26a31f9..82e7ecf 100644 --- a/Lib/db/User.php +++ b/Lib/db/User.php @@ -1,209 +1,209 @@ - 0) { - $cond = " AND $cond"; - } - - $sql = sprintf("SELECT TOP 1 - [FirstName] AS firstName, - [LastName] AS lastName, - [DisplayName] AS displayName, - [UserDefined1] AS freeOne, - [ActualAddressID] AS addressId, - [Email] AS mail, - [TelephoneMobile] AS mobilePhone, - [TelephonePrivate] AS privatePhone, - [Telephone] AS officePhone, - [%s] AS id, - REPLACE(UseraccountNr, ' ', '') AS login - FROM [%s] AS u - LEFT JOIN [%s] AS a ON a.[%s] = u.[ActualAddressID] - WHERE REPLACE(UseraccountNr, ' ', '') = '%s' AND disabled = 1 %s;", - self::$idColumn, self::$tableName, self::$addressTableName, self::$addressIdColumn, $login, $cond); - - $results = Connection::execute($sql, $raiseError); - return $results->current() !== false ? new User($results->current()) : null; - } - - private function _getCirculations($table, $sort = "ItemNr ASC") { - $sql = sprintf("SELECT - NoticeID, - CheckOutDate, - ItemNr - FROM %s AS c - INNER JOIN Items AS i ON i.ItemId = c.ItemId - WHERE - c.UseraccountId = %s - ORDER BY %s", $table, $this->id, $sort); - - $result = Connection::execute($sql); - - $circulations = $result->to_array(); - $books = array_map(function($c) { return $c['NoticeID']; }, $circulations); - $books = AudioBook::findBy('NoticeID', $books, true); - - foreach($circulations as $c) { - $books[$c['NoticeID']]['date'] = $c['CheckOutDate']; - $books[$c['NoticeID']]['itemNr'] = $c['ItemNr']; - } - - return $books; - } - - public function getCirculations() - { - return $this->_getCirculations('Circulations'); - } - - public function getOldCirculations() - { - return $this->_getCirculations('OldCirculations', 'CheckOutDate DESC'); - } - - /** - * Add a book to the wish list if it is not already inside. - - * @param string $noticeNr - * @return bool - */ - public function addWish($noticeNr) - { - if ($this->hasWish($noticeNr)) { - return false; - } - - $sql = "UPDATE Counters - SET WishID = WishID + 1 - OUTPUT INSERTED.WishID;"; - $result = Connection::execute($sql, true); - $row = $result->current(); - - $employee_id = Configuration::get('www_employee_id'); - $library_id = Configuration::get('www_library_id'); - $sql = sprintf("INSERT INTO %s - (WishID, NoticeID, %s, CreationDate, EmployeeID, BranchOfficeID, Remark, ModificationDate) - SELECT %s , NoticeID, %s, GETDATE() , %s , %s , '' , GETDATE() - FROM Notices - WHERE LTRIM(RTRIM(NoticeNr)) = '%s';", - User::$wishTableName, User::$idColumn, $row['WishID'], $this->id, $employee_id, $library_id, $noticeNr); - - Connection::execute($sql); - return true; - } - - /** - * Return true if the book is in the wish list - * @param string $noticeNr - * @return bool - */ - private function hasWish($noticeNr) - { - $sql = sprintf("SELECT w.NoticeID - FROM Wishes AS w - INNER JOIN Notices AS n ON n.NoticeID = w.NoticeID - WHERE - LTRIM(RTRIM(n.NoticeNr)) = '%s' - AND w.UseraccountId = %s;", $noticeNr, $this->id); - $result = Connection::execute($sql); - - return $result->current() !== false; - } - - /** - * Wishes are all the books that this user want to read. - * @param int $limit - * @return AudioBook[] - */ - public function getWishes($limit = 50) - { - $sql = sprintf("SELECT TOP $limit - NoticeID - FROM %s - WHERE %s = %s - ORDER BY CreationDate DESC" - ,User::$wishTableName, User::$idColumn, $this->id); - - $result = Connection::execute($sql); - $ids = array_map(function($r) { return $r['NoticeID']; }, $result->to_array()); - return AudioBook::findBy('NoticeID', $ids, true); - } - - /** - * Remove a book from the wish list - * @param string $noticeNr - */ - public function deleteWish($noticeNr) - { - $sql = sprintf("DELETE w - FROM %s AS w - INNER JOIN Notices AS n ON n.NoticeID = w.NoticeID - WHERE - LTRIM(RTRIM(n.NoticeNr)) = '%s' - AND %s = %s;", User::$wishTableName, $noticeNr, User::$idColumn, $this->id); - Connection::execute($sql, true); - } + 0) { + $cond = " AND $cond"; + } + + $sql = sprintf("SELECT TOP 1 + [FirstName] AS firstName, + [LastName] AS lastName, + [DisplayName] AS displayName, + [UserDefined1] AS freeOne, + [ActualAddressID] AS addressId, + [Email] AS mail, + [TelephoneMobile] AS mobilePhone, + [TelephonePrivate] AS privatePhone, + [Telephone] AS officePhone, + [%s] AS id, + REPLACE(UseraccountNr, ' ', '') AS login + FROM [%s] AS u + LEFT JOIN [%s] AS a ON a.[%s] = u.[ActualAddressID] + WHERE REPLACE(UseraccountNr, ' ', '') = '%s' AND disabled = 1 %s;", + self::$idColumn, self::$tableName, self::$addressTableName, self::$addressIdColumn, $login, $cond); + + $results = Connection::execute($sql, $raiseError); + return $results->current() !== false ? new User($results->current()) : null; + } + + private function _getCirculations($table, $sort = "ItemNr ASC") { + $sql = sprintf("SELECT + NoticeID, + CheckOutDate, + ItemNr + FROM %s AS c + INNER JOIN Items AS i ON i.ItemId = c.ItemId + WHERE + c.UseraccountId = %s + ORDER BY %s", $table, $this->id, $sort); + + $result = Connection::execute($sql); + + $circulations = $result->to_array(); + $books = array_map(function($c) { return $c['NoticeID']; }, $circulations); + $books = AudioBook::findBy('NoticeID', $books, true); + + foreach($circulations as $c) { + $books[$c['NoticeID']]['date'] = $c['CheckOutDate']; + $books[$c['NoticeID']]['itemNr'] = $c['ItemNr']; + } + + return $books; + } + + public function getCirculations() + { + return $this->_getCirculations('Circulations'); + } + + public function getOldCirculations() + { + return $this->_getCirculations('OldCirculations', 'CheckOutDate DESC'); + } + + /** + * Add a book to the wish list if it is not already inside. + + * @param string $noticeNr + * @return bool + */ + public function addWish($noticeNr) + { + if ($this->hasWish($noticeNr)) { + return false; + } + + $sql = "UPDATE Counters + SET WishID = WishID + 1 + OUTPUT INSERTED.WishID;"; + $result = Connection::execute($sql, true); + $row = $result->current(); + + $employee_id = Configuration::get('www_employee_id'); + $library_id = Configuration::get('www_library_id'); + $sql = sprintf("INSERT INTO %s + (WishID, NoticeID, %s, CreationDate, EmployeeID, BranchOfficeID, Remark, ModificationDate) + SELECT %s , NoticeID, %s, GETDATE() , %s , %s , '' , GETDATE() + FROM Notices + WHERE LTRIM(RTRIM(NoticeNr)) = '%s';", + User::$wishTableName, User::$idColumn, $row['WishID'], $this->id, $employee_id, $library_id, $noticeNr); + + Connection::execute($sql); + return true; + } + + /** + * Return true if the book is in the wish list + * @param string $noticeNr + * @return bool + */ + private function hasWish($noticeNr) + { + $sql = sprintf("SELECT w.NoticeID + FROM Wishes AS w + INNER JOIN Notices AS n ON n.NoticeID = w.NoticeID + WHERE + LTRIM(RTRIM(n.NoticeNr)) = '%s' + AND w.UseraccountId = %s;", $noticeNr, $this->id); + $result = Connection::execute($sql); + + return $result->current() !== false; + } + + /** + * Wishes are all the books that this user want to read. + * @param int $limit + * @return AudioBook[] + */ + public function getWishes($limit = 50) + { + $sql = sprintf("SELECT TOP $limit + NoticeID + FROM %s + WHERE %s = %s + ORDER BY CreationDate DESC" + ,User::$wishTableName, User::$idColumn, $this->id); + + $result = Connection::execute($sql); + $ids = array_map(function($r) { return $r['NoticeID']; }, $result->to_array()); + return AudioBook::findBy('NoticeID', $ids, true); + } + + /** + * Remove a book from the wish list + * @param string $noticeNr + */ + public function deleteWish($noticeNr) + { + $sql = sprintf("DELETE w + FROM %s AS w + INNER JOIN Notices AS n ON n.NoticeID = w.NoticeID + WHERE + LTRIM(RTRIM(n.NoticeNr)) = '%s' + AND %s = %s;", User::$wishTableName, $noticeNr, User::$idColumn, $this->id); + Connection::execute($sql, true); + } } \ No newline at end of file diff --git a/NetBiblio.php b/NetBiblio.php index c717c27..90aae08 100644 --- a/NetBiblio.php +++ b/NetBiblio.php @@ -1,561 +1,561 @@ -login = $login; - $this->client = $client; - } - - private function getUser($login = null) - { - if (!$login) { - $login = $_SESSION["user"]["login"]; - } - - $this->checkSession($login); - $user = User::find($this->login); - - if (!$user) { - throw new WebException ("UserNotFound", "cannot find account", -130); - } - - return $user; - } - - - private function GetBooks(array $codes) { - $bs = new BookSearch(); - $bs->addQuery('code:('.implode(' OR ', $codes).')', null, false); - $results = $bs->getResults(0, count($codes)); - return $results['books']; - } - - private function GetFiles(array $ids) - { - $ids = array_map('intval', $ids); - - $uri = sprintf("%s%s", - Configuration::get('checkfile_url'), - http_build_query(array("book" => implode(',', $ids))) - ); - - $ch = curl_init($uri); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HEADER, 0); - $json = curl_exec($ch); - curl_close($ch); - - return json_decode($json, true); - } - - private function SetFiles($files) { - $json = json_encode(array_values(array_map(function($f) { - return array( - 'id' => $f['id'], - 'samples' => array('set' => isset($f['samples']) ? $f['samples'] : array()), - 'zip' => array('set' => isset($f['zip']['uri']) ? $f['zip']['uri'] : ''), - 'zip_size' => array('set' => isset($f['zip']['size']) ? $f['zip']['size'] : 0), - ); - }, $files))); - - $uri = sprintf('%s:%s/%s/update?commitWithin=500', - Configuration::get('solr.server'), - Configuration::get('solr.port'), - Configuration::get('solr.path') - ); - $ch = curl_init($uri); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); - curl_setopt($ch, CURLOPT_POSTFIELDS, $json); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - 'Content-Type: application/json', - 'Content-Length: ' . strlen($json) - )); - - curl_exec($ch); - curl_close($ch); - } - - private function AddBookData(array $books) - { - if(isset($books['code'])) { - $result = $this->AddBookData(array($books)); - return reset($result); - } - - // add complementary data to each book - $books = array_map(function($b) { - // add files if we already have them - $files = array(); - if(isset($b['samples']) && count($b['zip']) > 0) { - $files['samples'] = $b['samples']; - } - unset($b['samples']); - if(isset($b['zip']) && strlen($b['zip']) > 0) { - $files['zip'] = array( - 'uri' => $b['zip'], - 'size' => $b['zip_size'], - ); - } - unset($b['zip']); - unset($b['zip_size']); - - if(! empty($files)) { - $b['files'] = $files; - } - - // add fields for mobile apps compatibility - $b['date'] = date('Y.m.d', strtotime($b['availabilityDate'])); - $b['readBy'] = isset($b['reader']) ? $b['reader'] : 'Lecteur inconnu'; - $b['category'] = $b['genre']; - $b['code3'] = $b['producerCode']; - $b['code3Long'] = $b['producer']; - $b['typeMedia1'] = $b['mediaType']; - - return $b; - }, $books); - - // retrieve files information for the book that don't have all of them at this time - $booksWithoutFiles = array_filter($books, function($b) { - return ! ( - isset($b['files']) && - isset($b['files']['samples']) && - count($b['files']['samples']) == 2 && // we want two samples (mp3 and ogg) - (strlen($this->login) == 0 || isset($b['files']['zip'])) // we want a zip file only for logged in people - ); - }); - - if(count($booksWithoutFiles) > 0) { - $ids = array_map(function($b) { return $b['code']; }, $booksWithoutFiles); - $files = $this->GetFiles($ids); - - if(count($files) > 0) { - foreach($booksWithoutFiles as $k => $b) { - $fileCode = sprintf("%05u", $b['code']); - if(isset($files[$fileCode])) { - $books[$k]['files'] = $files[$fileCode]; - $files[$fileCode]['id'] = $b['id']; - } else { - // we need to have an empty array for mobile apps compatibility. - $books[$k]['files'] = array(); - } - } - } - - $this->SetFiles($files); - } - - // add hash, client and login into zip file uri - $books = array_map(function($b) { - if(strlen($this->login) > 0 && isset($b['files']['zip']['uri'])) { - $key = 'babf2cfbe2633c3082f8cfffdb3d9008b4b3b300'; - $code = sprintf("%05u", $b['code']); - $hash = sha1($this->client.$this->login.$key.$code.date('Ymd')); - - $b['files']['zip']['uri'] = str_replace(array( - '{client}', - '{login}', - '{hash}', - ), array( - $this->client, - $this->login, - $hash, - ), $b['files']['zip']['uri']); - } else { - unset($b['files']['zip']); - } - - return $b; - }, $books); - - return $books; - } - - // ********************************** - // * Public methods * - // ********************************** - - public function AddDownloadLog($client, $login, $code) - { - $client = str_replace("'", "", $client); - $login = str_replace("'", "", $login); - $code = ltrim(str_replace("'", "", $code), '0'); - $itemNr = $code . 'V'; - - $sql = "SELECT itemID FROM Items WHERE LTRIM(RTRIM(ItemNr)) = '$itemNr';"; - $result = Connection::execute($sql, false); - if ($row = $result->current()) { - $itemId = $row['itemID']; - } else { - throw new WebException("ItemNotFound", "cannot find item", -1030); - } - - $sql = "SELECT UserAccountID FROM UserAccounts WHERE LTRIM(RTRIM(UserAccountNr)) = '$login';"; - $result = Connection::execute($sql, false); - if ($row = $result->current()) { - $userId = $row['UserAccountID']; - } else { - throw new WebException("UserNotFound", "cannot find user", -1031); - } - - $sql = "SELECT circulationId - FROM OldCirculations - WHERE - useraccountID= $userId AND - itemID = $itemId AND - LTRIM(RTRIM(remark)) = '$client';"; - $result = Connection::execute($sql, false); - - if ($row = $result->current()) { - $id = $row['circulationId']; - $sql = "UPDATE OldCirculations - SET - CheckInDate=GETDATE(), - CheckOutDate=GETDATE() - WHERE circulationID = $id"; - Connection::execute($sql); - return true; - } - - $sql = "SELECT TOP 1 circulationID FROM OldCirculations ORDER BY CirculationID DESC"; - $result = Connection::execute($sql, false); - if ($row = $result->current()) { - $nextId = $row['circulationID'] + 1; - } else { - $nextId = 1; - } - - $sql = "UPDATE Useraccounts - SET - Circulations = Circulations + 1, - TotalCirculations = TotalCirculations + 1 - WHERE UseraccountID = $userId;"; - Connection::execute($sql); - - $sql = "UPDATE Items - SET - Circulations = Circulations + 1, - TotalCirculations = TotalCirculations + 1 - WHERE ItemID = $itemId;"; - Connection::execute($sql); - - $worker_id = Configuration::get('netbiblio_worker_id'); - $sql = "INSERT INTO OldCirculations ( - CirculationID, ItemID, UseraccountID, - Remark, - DueDate, CheckOutDate, CheckInDate, - CheckOutBranchofficeID, CheckOutEmployeeID, CheckInBranchofficeID, CheckInEmployeeID, - Reminders, Renewals, Prereminder, InfoCode, CheckOutSIP2Info, CheckInSIP2Info - ) VALUES ( - $nextId, $itemId, $userId, - '$client', - DATEADD(month, 2, GETDATE()), GETDATE(), GETDATE(), - 2, $worker_id, 2, $worker_id, - 0, 0, 1, '-', 1, 1 - );"; - Connection::execute($sql); - return true; - } - - public function Authenticate($login, $password, $client = "website") - { - session_unset(); /* destroy all session vars */ - - $user = User::authenticate($login, $password); - - if (!$user) { - throw new WebException ("AuthenticateBad", "authentication failed", -100); - } - - $_SESSION["user"]["login"] = $login; - $_SESSION["user"]["client"] = $client; - - $this->login = $login; - $this->client = $client; - - return $user->toArray(); - } - - public function Disconnect() - { - $_SESSION = array(); - - if (ini_get("session.use_cookies")) { - $params = session_get_cookie_params(); - setcookie(session_name(), '', time() - 42000, - $params["path"], $params["domain"], - $params["secure"], $params["httponly"]); - } - - return array(); - } - - public function IsAuthenticated() - { - return $this->getUser()->toArray(); - } - - public function FindAccount($login) - { - return $this->getUser($login)->toArray(); - } - - public function GetWishes() - { - $books = $this->getUser()->getWishes(); - return array_values($this->AddBookData($books)); - } - - public function GetCirculations() - { - $circulations = $this->getUser()->getCirculations(); - return array_values($this->AddBookData($circulations)); - } - - public function GetOldCirculations() - { - $circulations = $this->getUser()->getOldCirculations(); - return array_values($this->AddBookData($circulations)); - } - - public function AddWish($bookNr) - { - return $this->getUser()->addWish($bookNr); - } - - public function DeleteWish($bookNr) - { - $this->getUser()->deleteWish($bookNr); - } - - public function FindBooks($codes) - { - $this->CheckSession(); - - $codes = json_decode($codes); - - // it is faster to do multiple small request to Solr rather than one big so separate - // in chunks if we are above the limit. 15 was found by testing and seems to be a sweet spot - $limit = 15; - if(count($codes) > $limit) { - $parts = array_chunk($codes, $limit); - $books = array(); - foreach($parts as $p) { - $books = array_merge($books, $this->GetBooks($p)); - } - } else { - $books = $this->GetBooks($codes); - } - - return $this->AddBookData($books); - } - - public function FindBook($code) - { - $this->CheckSession(); - return reset($this->AddBookData($this->GetBooks(array($code)))); - } - - public function GetRandomBooks($number = 100, $seed = null) { - if(is_null($seed)) { - $seed = time(); - } - - $bs = new BookSearch(); - $bs->addSortField('random_'.$seed); - $results = $bs->getResults(0, $number); - return $this->AddBookData($results['books']); - } - - public function Search($query, $start, $limit) - { - $query = array( - 'queryText' => $query, - 'queryType' => is_numeric($query) && strlen($query) <= 5 ? 'code' : 'text', - 'count' => $limit, - 'page' => max(intval($start) - 1, 0), - ); - $data = $this->NewSearch(json_encode($query)); - - // remove fields that are not used in "old" search - unset($data['count']); - unset($data['facets']); - return $data; - } - - public function NewSearch($values) - { - $this->CheckSession(); - - $queryArray = json_decode($values, true); - if(! is_array($queryArray)) { - throw new WebException("CallArg", "Argument must be valid JSON.", -42); - } - - // The iOS and Android applications still uses 'category' instead of 'genre' - if(isset($queryArray['category']) && is_array($queryArray['category'])) { - $queryArray['genre'] = $queryArray['category']; - unset($queryArray['category']); - } - - $bs = new BookSearch(); - - if (isset($queryArray['queryType'])) { - $bs->addSortField('author', \SolrQuery::ORDER_ASC); - $bs->addSortField('title', \SolrQuery::ORDER_ASC); - $bs->addSortField('producerCode'); - $bs->addSortField('mediaType', \SolrQuery::ORDER_ASC); - } else { - $bs->addSortField('availabilityDate'); - $bs->addSortField('author', \SolrQuery::ORDER_ASC); - $bs->addSortField('title', \SolrQuery::ORDER_ASC); - } - - if (isset($queryArray['queryText']) && strlen($queryArray['queryText']) > 0) { - $type = isset($queryArray['queryType']) ? $queryArray['queryType'] : null; - - if($this->client != 'website' && in_array($type, array('title', 'author', 'reader'))) { - // we don't want an exact search on mobile apps - $type = $type.'_fr'; - } - - $bs->addQuery($queryArray['queryText'], $type); - } - - if(isset($queryArray['genre']) && is_array($queryArray['genre'])) { - $selectedGenres = array_filter($queryArray['genre'], function ($c) { - return $c != '0'; - }); - if (count($selectedGenres) > 0) { - $selectedGenres = array_map(function ($c) { - return 'genreCode:'.\SolrUtils::escapeQueryChars($c); - }, $selectedGenres); - $bs->addQuery('('.implode(' OR ', $selectedGenres).')', null, false); - } - } - - if(isset($queryArray['jeunesse']) && $queryArray['jeunesse']['filtrer'] === 'filtrer') { - $bs->addQuery(1, 'jeunesse'); - } - - // The following query filter is used by the mobile applications - if(isset($queryArray['producer']) && strlen($queryArray['producer']) > 0) { - $bs->addQuery($queryArray['producer'], 'producerCode'); - } - - $count = isset($queryArray['count']) ? (int) $queryArray['count'] : Configuration::get('solr.result_count'); - $start = isset($queryArray['page']) ? $queryArray['page'] * $count : 0; - - $results = $bs->getResults($start, $count); - $data = array( - 'count' => $results['count'], - 'facets' => $results['facets'], - ); - - return array_merge($data, $this->AddBookData($results['books'])); - } - - public function ListOfReaders() - { - return AudioBook::listOfReaders(); - } - - /** - * This method is called by the website in Drupal to get the list - * of available 'Genres'. - * @return array - */ - public function ListOfGenres() - { - return AudioBook::ListOfGenres(); - } - - /** - * This method is called by the Android application to get the list - * of available 'Genres'. - * @return array - */ - public function ListOfCategories() - { - return AudioBook::ListOfGenres(); - } - - /** - * This method is called by the iOS application to get the list - * of available 'Genres'. 'Jeunesse' must be a part of them. - * @return array - */ - public function ListOfTypes() - { - return array_map(function($g) { - return $g['text']; - }, AudioBook::ListOfGenres(true)); - } - - public function InReadingBooks() - { - return AudioBook::inReading(); - } - - /** - * This method is used by the iOS application to retrieve the last - * books for a given Genre. It may receives the genre 'Jeunesse' which - * is a boolean value on Solr documents. - * - * @param $genre - * @param $number - * @return array - * @throws WebException - */ - public function LastBooksByType($genre, $number) - { - $this->CheckSession(); - - $s = new BookSearch(); - if($genre == 'Jeunesse') { - $s->addQuery(1, 'jeunesse'); - } else { - $s->addQuery($genre, 'genre'); - } - $s->addSortField('availabilityDate'); - - $results = $s->getResults(0, $number); - $books = $this->AddBookData($results['books']); - - $data = array(); - foreach($books as $b) { - $data[$genre][] = $b; - } - return $data; - } -} +login = $login; + $this->client = $client; + } + + private function getUser($login = null) + { + if (!$login) { + $login = $_SESSION["user"]["login"]; + } + + $this->checkSession($login); + $user = User::find($this->login); + + if (!$user) { + throw new WebException ("UserNotFound", "cannot find account", -130); + } + + return $user; + } + + + private function GetBooks(array $codes) { + $bs = new BookSearch(); + $bs->addQuery('code:('.implode(' OR ', $codes).')', null, false); + $results = $bs->getResults(0, count($codes)); + return $results['books']; + } + + private function GetFiles(array $ids) + { + $ids = array_map('intval', $ids); + + $uri = sprintf("%s%s", + Configuration::get('checkfile_url'), + http_build_query(array("book" => implode(',', $ids))) + ); + + $ch = curl_init($uri); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, 0); + $json = curl_exec($ch); + curl_close($ch); + + return json_decode($json, true); + } + + private function SetFiles($files) { + $json = json_encode(array_values(array_map(function($f) { + return array( + 'id' => $f['id'], + 'samples' => array('set' => isset($f['samples']) ? $f['samples'] : array()), + 'zip' => array('set' => isset($f['zip']['uri']) ? $f['zip']['uri'] : ''), + 'zip_size' => array('set' => isset($f['zip']['size']) ? $f['zip']['size'] : 0), + ); + }, $files))); + + $uri = sprintf('%s:%s/%s/update?commitWithin=500', + Configuration::get('solr.server'), + Configuration::get('solr.port'), + Configuration::get('solr.path') + ); + $ch = curl_init($uri); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen($json) + )); + + curl_exec($ch); + curl_close($ch); + } + + private function AddBookData(array $books) + { + if(isset($books['code'])) { + $result = $this->AddBookData(array($books)); + return reset($result); + } + + // add complementary data to each book + $books = array_map(function($b) { + // add files if we already have them + $files = array(); + if(isset($b['samples']) && count($b['zip']) > 0) { + $files['samples'] = $b['samples']; + } + unset($b['samples']); + if(isset($b['zip']) && strlen($b['zip']) > 0) { + $files['zip'] = array( + 'uri' => $b['zip'], + 'size' => $b['zip_size'], + ); + } + unset($b['zip']); + unset($b['zip_size']); + + if(! empty($files)) { + $b['files'] = $files; + } + + // add fields for mobile apps compatibility + $b['date'] = date('Y.m.d', strtotime($b['availabilityDate'])); + $b['readBy'] = isset($b['reader']) ? $b['reader'] : 'Lecteur inconnu'; + $b['category'] = $b['genre']; + $b['code3'] = $b['producerCode']; + $b['code3Long'] = $b['producer']; + $b['typeMedia1'] = $b['mediaType']; + + return $b; + }, $books); + + // retrieve files information for the book that don't have all of them at this time + $booksWithoutFiles = array_filter($books, function($b) { + return ! ( + isset($b['files']) && + isset($b['files']['samples']) && + count($b['files']['samples']) == 2 && // we want two samples (mp3 and ogg) + (strlen($this->login) == 0 || isset($b['files']['zip'])) // we want a zip file only for logged in people + ); + }); + + if(count($booksWithoutFiles) > 0) { + $ids = array_map(function($b) { return $b['code']; }, $booksWithoutFiles); + $files = $this->GetFiles($ids); + + if(count($files) > 0) { + foreach($booksWithoutFiles as $k => $b) { + $fileCode = sprintf("%05u", $b['code']); + if(isset($files[$fileCode])) { + $books[$k]['files'] = $files[$fileCode]; + $files[$fileCode]['id'] = $b['id']; + } else { + // we need to have an empty array for mobile apps compatibility. + $books[$k]['files'] = array(); + } + } + } + + $this->SetFiles($files); + } + + // add hash, client and login into zip file uri + $books = array_map(function($b) { + if(strlen($this->login) > 0 && isset($b['files']['zip']['uri'])) { + $key = 'babf2cfbe2633c3082f8cfffdb3d9008b4b3b300'; + $code = sprintf("%05u", $b['code']); + $hash = sha1($this->client.$this->login.$key.$code.date('Ymd')); + + $b['files']['zip']['uri'] = str_replace(array( + '{client}', + '{login}', + '{hash}', + ), array( + $this->client, + $this->login, + $hash, + ), $b['files']['zip']['uri']); + } else { + unset($b['files']['zip']); + } + + return $b; + }, $books); + + return $books; + } + + // ********************************** + // * Public methods * + // ********************************** + + public function AddDownloadLog($client, $login, $code) + { + $client = str_replace("'", "", $client); + $login = str_replace("'", "", $login); + $code = ltrim(str_replace("'", "", $code), '0'); + $itemNr = $code . 'V'; + + $sql = "SELECT itemID FROM Items WHERE LTRIM(RTRIM(ItemNr)) = '$itemNr';"; + $result = Connection::execute($sql, false); + if ($row = $result->current()) { + $itemId = $row['itemID']; + } else { + throw new WebException("ItemNotFound", "cannot find item", -1030); + } + + $sql = "SELECT UserAccountID FROM UserAccounts WHERE LTRIM(RTRIM(UserAccountNr)) = '$login';"; + $result = Connection::execute($sql, false); + if ($row = $result->current()) { + $userId = $row['UserAccountID']; + } else { + throw new WebException("UserNotFound", "cannot find user", -1031); + } + + $sql = "SELECT circulationId + FROM OldCirculations + WHERE + useraccountID= $userId AND + itemID = $itemId AND + LTRIM(RTRIM(remark)) = '$client';"; + $result = Connection::execute($sql, false); + + if ($row = $result->current()) { + $id = $row['circulationId']; + $sql = "UPDATE OldCirculations + SET + CheckInDate=GETDATE(), + CheckOutDate=GETDATE() + WHERE circulationID = $id"; + Connection::execute($sql); + return true; + } + + $sql = "SELECT TOP 1 circulationID FROM OldCirculations ORDER BY CirculationID DESC"; + $result = Connection::execute($sql, false); + if ($row = $result->current()) { + $nextId = $row['circulationID'] + 1; + } else { + $nextId = 1; + } + + $sql = "UPDATE Useraccounts + SET + Circulations = Circulations + 1, + TotalCirculations = TotalCirculations + 1 + WHERE UseraccountID = $userId;"; + Connection::execute($sql); + + $sql = "UPDATE Items + SET + Circulations = Circulations + 1, + TotalCirculations = TotalCirculations + 1 + WHERE ItemID = $itemId;"; + Connection::execute($sql); + + $worker_id = Configuration::get('netbiblio_worker_id'); + $sql = "INSERT INTO OldCirculations ( + CirculationID, ItemID, UseraccountID, + Remark, + DueDate, CheckOutDate, CheckInDate, + CheckOutBranchofficeID, CheckOutEmployeeID, CheckInBranchofficeID, CheckInEmployeeID, + Reminders, Renewals, Prereminder, InfoCode, CheckOutSIP2Info, CheckInSIP2Info + ) VALUES ( + $nextId, $itemId, $userId, + '$client', + DATEADD(month, 2, GETDATE()), GETDATE(), GETDATE(), + 2, $worker_id, 2, $worker_id, + 0, 0, 1, '-', 1, 1 + );"; + Connection::execute($sql); + return true; + } + + public function Authenticate($login, $password, $client = "website") + { + session_unset(); /* destroy all session vars */ + + $user = User::authenticate($login, $password); + + if (!$user) { + throw new WebException ("AuthenticateBad", "authentication failed", -100); + } + + $_SESSION["user"]["login"] = $login; + $_SESSION["user"]["client"] = $client; + + $this->login = $login; + $this->client = $client; + + return $user->toArray(); + } + + public function Disconnect() + { + $_SESSION = array(); + + if (ini_get("session.use_cookies")) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, + $params["path"], $params["domain"], + $params["secure"], $params["httponly"]); + } + + return array(); + } + + public function IsAuthenticated() + { + return $this->getUser()->toArray(); + } + + public function FindAccount($login) + { + return $this->getUser($login)->toArray(); + } + + public function GetWishes() + { + $books = $this->getUser()->getWishes(); + return array_values($this->AddBookData($books)); + } + + public function GetCirculations() + { + $circulations = $this->getUser()->getCirculations(); + return array_values($this->AddBookData($circulations)); + } + + public function GetOldCirculations() + { + $circulations = $this->getUser()->getOldCirculations(); + return array_values($this->AddBookData($circulations)); + } + + public function AddWish($bookNr) + { + return $this->getUser()->addWish($bookNr); + } + + public function DeleteWish($bookNr) + { + $this->getUser()->deleteWish($bookNr); + } + + public function FindBooks($codes) + { + $this->CheckSession(); + + $codes = json_decode($codes); + + // it is faster to do multiple small request to Solr rather than one big so separate + // in chunks if we are above the limit. 15 was found by testing and seems to be a sweet spot + $limit = 15; + if(count($codes) > $limit) { + $parts = array_chunk($codes, $limit); + $books = array(); + foreach($parts as $p) { + $books = array_merge($books, $this->GetBooks($p)); + } + } else { + $books = $this->GetBooks($codes); + } + + return $this->AddBookData($books); + } + + public function FindBook($code) + { + $this->CheckSession(); + return reset($this->AddBookData($this->GetBooks(array($code)))); + } + + public function GetRandomBooks($number = 100, $seed = null) { + if(is_null($seed)) { + $seed = time(); + } + + $bs = new BookSearch(); + $bs->addSortField('random_'.$seed); + $results = $bs->getResults(0, $number); + return $this->AddBookData($results['books']); + } + + public function Search($query, $start, $limit) + { + $query = array( + 'queryText' => $query, + 'queryType' => is_numeric($query) && strlen($query) <= 5 ? 'code' : 'text', + 'count' => $limit, + 'page' => max(intval($start) - 1, 0), + ); + $data = $this->NewSearch(json_encode($query)); + + // remove fields that are not used in "old" search + unset($data['count']); + unset($data['facets']); + return $data; + } + + public function NewSearch($values) + { + $this->CheckSession(); + + $queryArray = json_decode($values, true); + if(! is_array($queryArray)) { + throw new WebException("CallArg", "Argument must be valid JSON.", -42); + } + + // The iOS and Android applications still uses 'category' instead of 'genre' + if(isset($queryArray['category']) && is_array($queryArray['category'])) { + $queryArray['genre'] = $queryArray['category']; + unset($queryArray['category']); + } + + $bs = new BookSearch(); + + if (isset($queryArray['queryType'])) { + $bs->addSortField('author', \SolrQuery::ORDER_ASC); + $bs->addSortField('title', \SolrQuery::ORDER_ASC); + $bs->addSortField('producerCode'); + $bs->addSortField('mediaType', \SolrQuery::ORDER_ASC); + } else { + $bs->addSortField('availabilityDate'); + $bs->addSortField('author', \SolrQuery::ORDER_ASC); + $bs->addSortField('title', \SolrQuery::ORDER_ASC); + } + + if (isset($queryArray['queryText']) && strlen($queryArray['queryText']) > 0) { + $type = isset($queryArray['queryType']) ? $queryArray['queryType'] : null; + + if($this->client != 'website' && in_array($type, array('title', 'author', 'reader'))) { + // we don't want an exact search on mobile apps + $type = $type.'_fr'; + } + + $bs->addQuery($queryArray['queryText'], $type); + } + + if(isset($queryArray['genre']) && is_array($queryArray['genre'])) { + $selectedGenres = array_filter($queryArray['genre'], function ($c) { + return $c != '0'; + }); + if (count($selectedGenres) > 0) { + $selectedGenres = array_map(function ($c) { + return 'genreCode:'.\SolrUtils::escapeQueryChars($c); + }, $selectedGenres); + $bs->addQuery('('.implode(' OR ', $selectedGenres).')', null, false); + } + } + + if(isset($queryArray['jeunesse']) && $queryArray['jeunesse']['filtrer'] === 'filtrer') { + $bs->addQuery(1, 'jeunesse'); + } + + // The following query filter is used by the mobile applications + if(isset($queryArray['producer']) && strlen($queryArray['producer']) > 0) { + $bs->addQuery($queryArray['producer'], 'producerCode'); + } + + $count = isset($queryArray['count']) ? (int) $queryArray['count'] : Configuration::get('solr.result_count'); + $start = isset($queryArray['page']) ? $queryArray['page'] * $count : 0; + + $results = $bs->getResults($start, $count); + $data = array( + 'count' => $results['count'], + 'facets' => $results['facets'], + ); + + return array_merge($data, $this->AddBookData($results['books'])); + } + + public function ListOfReaders() + { + return AudioBook::listOfReaders(); + } + + /** + * This method is called by the website in Drupal to get the list + * of available 'Genres'. + * @return array + */ + public function ListOfGenres() + { + return AudioBook::ListOfGenres(); + } + + /** + * This method is called by the Android application to get the list + * of available 'Genres'. + * @return array + */ + public function ListOfCategories() + { + return AudioBook::ListOfGenres(); + } + + /** + * This method is called by the iOS application to get the list + * of available 'Genres'. 'Jeunesse' must be a part of them. + * @return array + */ + public function ListOfTypes() + { + return array_map(function($g) { + return $g['text']; + }, AudioBook::ListOfGenres(true)); + } + + public function InReadingBooks() + { + return AudioBook::inReading(); + } + + /** + * This method is used by the iOS application to retrieve the last + * books for a given Genre. It may receives the genre 'Jeunesse' which + * is a boolean value on Solr documents. + * + * @param $genre + * @param $number + * @return array + * @throws WebException + */ + public function LastBooksByType($genre, $number) + { + $this->CheckSession(); + + $s = new BookSearch(); + if($genre == 'Jeunesse') { + $s->addQuery(1, 'jeunesse'); + } else { + $s->addQuery($genre, 'genre'); + } + $s->addSortField('availabilityDate'); + + $results = $s->getResults(0, $number); + $books = $this->AddBookData($results['books']); + + $data = array(); + foreach($books as $b) { + $data[$genre][] = $b; + } + return $data; + } +} diff --git a/README.md b/README.md index a889953..5268aaa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# README # - -This repository contains the code for the WebService used internally at the [BSR](http://bibliothequesonore.ch/) by the Drupal website and both the iOS and Android applications. - +# README # + +This repository contains the code for the WebService used internally at the [BSR](http://bibliothequesonore.ch/) by the Drupal website and both the iOS and Android applications. + For more information, please consult the internal documentation : http://192.168.1.250/dokuwiki/doku.php?id=webservice \ No newline at end of file diff --git a/index.php b/index.php index a0935a4..de230c4 100644 --- a/index.php +++ b/index.php @@ -1,20 +1,20 @@ -Run(); +Run(); diff --git a/mobile.php b/mobile.php index 2daa4aa..a600d04 100644 --- a/mobile.php +++ b/mobile.php @@ -1,4 +1,4 @@ -