From a9190fe282fb3306f6f91fcee8737b7f50a4e444 Mon Sep 17 00:00:00 2001 From: Gilles Crettenand Date: Thu, 7 May 2015 10:07:22 +0200 Subject: [PATCH] first blood --- .gitignore | 2 + configuration.php | 98 ++++++++ global.php | 4 + lib/AudioBook.php | 574 ++++++++++++++++++++++++++++++++++++++++++ lib/BookSearch.php | 78 ++++++ lib/Connection.php | 205 +++++++++++++++ lib/DbMapping.php | 136 ++++++++++ lib/User.php | 282 +++++++++++++++++++++ mobile.netbiblio.php | 402 +++++++++++++++++++++++++++++ mobile.php | 6 + mobile.webservice.php | 109 ++++++++ 11 files changed, 1896 insertions(+) create mode 100644 .gitignore create mode 100644 configuration.php create mode 100644 global.php create mode 100644 lib/AudioBook.php create mode 100644 lib/BookSearch.php create mode 100644 lib/Connection.php create mode 100644 lib/DbMapping.php create mode 100644 lib/User.php create mode 100644 mobile.netbiblio.php create mode 100644 mobile.php create mode 100644 mobile.webservice.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bd5ce9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +*configuration.local.php diff --git a/configuration.php b/configuration.php new file mode 100644 index 0000000..2568c98 --- /dev/null +++ b/configuration.php @@ -0,0 +1,98 @@ + array( + 'driver' => 'SQL Server Native Client 11.0', + 'server' => 'BSR2012\SQLEXPRESS,1433', + 'username' => 'alcoda', + 'password' => 'alcodaonly', + 'name' => 'NetBiblio3', + ), + 'solr' => array( + 'server' => '192.168.1.250', + 'port' => '8983', + 'username' => '', + 'password' => '', + 'result_count' => 10, + ), + 'log' => array( + 'file' => 'c:\\logs\\ws_ipad.txt', + 'active' => false, + ), + '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/global.php b/global.php new file mode 100644 index 0000000..4cdb807 --- /dev/null +++ b/global.php @@ -0,0 +1,4 @@ +loadDetails(); + } + } + + public function loadDetails() + { + if ($this->loaded) { + return; + } + $this->editor = ''; + $this->summary = ''; + $this->media = ''; + $this->isbn = ''; + $this->collection = ''; + $this->category = ''; + $this->cover = ''; + + $sql_string = ''; + + // Introducing the cast as varchar(8000) to avoid the unixODBC LongText bug.. + $sql_string .= "SELECT Tag, SubfieldCode, ContentShortPart, cast(ContentLongPart as varchar(3000)) as ContentLongPart, DisplayText "; + $sql_string .= "FROM NoticeFields "; + $sql_string .= "LEFT JOIN Authorities ON Authorities.AuthorityID = NoticeFields.AuthorityID "; + $sql_string .= "WHERE NoticeID = " . $this->id . " "; + $sql_string .= "ORDER BY Tag ASC, TagRepetition ASC, SubfieldCodeNr ASC;"; + + //print $sql_string; + + // ajout de l'url de l'image amazon + + $result = Connection::execute($sql_string, true); + while ($row = $result->next()) { + + switch ($row['Tag']) { + case '520': + $this->summary = $this->addDetailToField(convertMSChar($this->summary), $row); + break; + case '260': + $this->editor = $this->addDetailToField($this->editor, $row); + break; + case '300': + $this->media = $this->addDetailToField($this->media, $row); + break; + case '020': + $this->isbn = $this->addDetailToField($this->isbn, $row); + break; + case '490': + $this->collection = $this->addDetailToField($this->collection, $row); + break; + case '600': + case '610': + case '650': + case '651': + case '655': + case '690': + case '691': + case '695': + case '696': + $this->category = $this->addDetailToField($this->category, $row); + break; + case '901': + $this->readBy = $this->addDetailToField($this->readBy, $row); + break; + case '856': + if ($row['SubfieldCode'] == 'u') { + $this->link = $this->addDetailToField($this->link, $row); + } else if ($row['SubfieldCode'] == 'z') { + $this->linkTitle = $this->addDetailToField($this->linkTitle, $row); + } + break; + case '899': + if ($row['SubfieldCode'] == 'a') { + $this->cover = "http://ecx.images-amazon.com/images/I/" . $row['ContentShortPart'] . "._SL320_.jpg"; + } + } + + if ($this->coverdisplay == 2) { + $this->cover = "http://fichiers.bsr-lausanne.ch:8089/Netbiblio3/images/covers/" . "Cover" . $this->id . "_Original.jpg"; + } + } + + $this->loaded = true; + } + + private function addDetailToField($actualValue, $row) + { + $actualValue = ($actualValue ? $actualValue . ' ' : ''); + $actualValue .= $row['ContentShortPart'] . $row['ContentLongPart'] . $row['DisplayText']; + return $actualValue; + } + + /** + * If $id is an array, return an array of book, if it is only one id, return one book + * Load books details only if not an array. + * + * Beware that this search use the NoticeID, not the NoticeNr, if you need the last one + * use findByCode method instead; + * @param int|array $id + * @return AudioBook|AudioBook[] + */ + public static function find($id) + { + $id = str_replace("'", "''", $id); + $fullIdColumn = 'ab.'.AudioBook::$idColumn; + if (is_array($id)) { + if (count($id) > 0) { + $condition = $fullIdColumn . ' IN (' . implode(', ', $id) . ')'; + } else { + $condition = "$fullIdColumn IS NULL"; // bad way of returning an empty ResultSet + } + $top = 'DISTINCT '; + } else { + $condition = "$fullIdColumn = $id"; + $top = 'TOP 1'; + } + + $sql = sprintf("SELECT %s + ab.[%s] AS id, + [Title] AS title, + [Author] AS author, + LTRIM(RTRIM(NoticeNr)) AS code, + RTRIM(userdefined3code) AS code3, + [code3Code].TextFre AS code3Long, + [genreCode].TextFre AS genre, + [genreCode].Code AS genreCode, + [coverdisplay], + [MediaType1Code] AS typeMedia1, + 12 AS score, + CONVERT(varchar, CreationDate, 102) AS date + FROM [%s] AS ab + INNER JOIN Codes code3Code ON ab.userdefined3code = code3Code.Code AND code3Code.Type=6 + INNER JOIN Codes genreCode ON MediaType2Code = genreCode.Code AND genreCode.Type = 2 + WHERE %s AND LTRIM(RTRIM(noticenr)) NOT LIKE '%%~%%' ORDER BY Author, Title + ;", $top, self::$idColumn, self::$tableName, $condition); + + $result_set = Connection::execute($sql, true); + + if (is_array($id)) { + $result = array(); + foreach ($id as $book_id) { + while ($row = $result_set->next()) { + if ($row['id'] == $book_id) { + $result[] = new AudioBook($row, TRUE); + break; + } + } + $result_set->rewind(); + } + } else { + $row = $result_set->next(); + if (!$row) { + return null; + // throw new Exception("NotFoundException"); + } + + $result = new AudioBook($row, TRUE); + } + + return $result; + } + + /** + * Return a book or an array of book given their notice number without any letter. + * + * if $code is an array, return an array of book. If not, return the first match. + * + * Due to the fact that notice number is incosistent in the Notices table, + * we use the si_sentences view instead. + * + * @param string $code + * @return AudioBook + * @throws Exception + * @throws SqlException + */ + public static function findByCode3($code) + { + + $fullIdColumn = AudioBook::$tableName . '.' . AudioBook::$idColumn; + if (is_array($code)) { + + $value = str_replace("'", "''", implode(" ", $code)); + $value = str_replace(' ', "', '", $value); + $top = ''; + $condition = "si_sentences.[Key] IN ('" . $value . "')"; + } else { + + $value = str_replace("'", "''", $code); + $top = 'top 1'; + $condition = "si_sentences.[Key] = '$value'"; + } + + $sql_string = ''; + $sql_string .= "SELECT $top $fullIdColumn as id, "; + $sql_string .= "Title as title, Author as author, si_sentences.[Key] as code, "; + $sql_string .= "Codes.TextFre As type, "; + $sql_string .= "0 AS score "; + $sql_string .= "FROM " . AudioBook::$tableName; + $sql_string .= " INNER JOIN Codes ON " . AudioBook::$tableName . ".MediaType2Code = Codes.Code AND Codes.Type = 2 "; + $sql_string .= " INNER JOIN si_sentences ON si_sentences.NoticeID = Notices.NoticeID"; + $sql_string .= " AND si_sentences.Category = 'Code'"; + $sql_string .= " WHERE ($condition) ORDER BY author, title;"; + + $result_set = Connection::execute($sql_string, false); + + if (is_array($code)) { + $result = array(); + foreach ($code as $book_code) { + while ($row = $result_set->next()) { + if ($row['code'] == $book_code) { + $result[] = new AudioBook($row, FALSE); + break; + } + } + $result_set->rewind(); + } + } else { + $row = $result_set->next(); + if (!$row) { + throw new Exception("NotFoundException"); + } + $result = new AudioBook($row, TRUE); + } + + return $result; + } + + + public static function findByCode($code) + { + + $fullIdColumn = AudioBook::$tableName . '.' . AudioBook::$idColumn; + + if (is_array($code)) { + + $value = str_replace("'", "''", implode(" ", $code)); + $value = str_replace(' ', "', '", $value); + $top = ''; + $condition = "LTRIM(RTRIM(NoticeNr)) in ('" . $value . "')"; + } else { + + $value = str_replace("'", "''", $code); + $top = 'top 1'; + $condition = "LTRIM(RTRIM(NoticeNr)) = '$value'"; + } + + $sql_string = ''; + $sql_string .= "SELECT $top $fullIdColumn as id, "; + $sql_string .= "Notices.Title as title, Notices.Author as author, LTRIM(RTRIM(NoticeNr)) as code, "; + $sql_string .= "Codes.TextFre As type, "; + $sql_string .= "0 AS score "; + $sql_string .= "FROM " . AudioBook::$tableName; + $sql_string .= " INNER JOIN Codes ON " . AudioBook::$tableName . ".MediaType2Code = Codes.Code AND Codes.Type = 2 "; + $sql_string .= " WHERE ($condition) ORDER BY author, title ;"; + + + $result_set = Connection::execute($sql_string, false); + + if (is_array($code)) { + + $result = array(); + + while ($row = $result_set->next()) { + $result[] = new AudioBook($row, true); + + } + + return $result; + } else { + $row = $result_set->next(); + if (!$row) { + + throw new Exception("NotFoundException"); + } + $result[] = new AudioBook($row, true); + + } + return $result; + } + + /** + * @param $code + * @return int + * @throws Exception + * @throws SqlException + */ + public static function findIdByCode($code) + { + $sql = "SELECT NoticeId FROM Notices WHERE LTRIM(RTRIM(NoticeNr)) = '$code';"; + $result = Connection::execute($sql, false); + if ($result === false || $result->length == 0) { + throw new Exception("NotFoundException"); + } + + $row = $result->current(); + return $row['NoticeId']; + } + + /** + * Retrieve the last books for each type. If 'type' is an empty string, then + * it returns all types. + */ + public static function lastBooksByType($type, $itemsByGroup = 10) + { + $type_before_escape = $type; + $type = Connection::escape(utf8_decode($type)); + + if ($type_before_escape == "Jeunesse") { + if ($itemsByGroup < 20) + $itemsByGroup = 20; + + $sqlQuery = " SELECT top " . $itemsByGroup . " Notices.NoticeId AS id, "; + $sqlQuery .= " Notices.title AS title, "; + $sqlQuery .= " Notices.author AS author, "; + $sqlQuery .= " LTRIM(RTRIM(Notices.NoticeNr)) AS code, "; + $sqlQuery .= " 'Jeunesse' AS type, 0 as score, "; + $sqlQuery .= " convert(varchar, CreationDate, 102) as date "; + $sqlQuery .= " FROM Notices "; + $sqlQuery .= " WHERE Notices.NoticeNr NOT LIKE '%~%' "; + $sqlQuery .= " AND Notices.NoticeNr NOT LIKE '%V%' "; + $sqlQuery .= " AND Notices.NoticeNr NOT LIKE '%T%' "; + $sqlQuery .= " AND Notices.MediaType1Code = 'CDD' "; + $sqlQuery .= " AND Notices.Visible = 1 "; + $sqlQuery .= " AND Notices.AgeCode in ('E', 'J') "; + $sqlQuery .= " ORDER BY date DESC;"; + + } else { + + $sqlQuery = "WITH cte AS ( "; + $sqlQuery .= " SELECT Notices.NoticeId AS id, "; + $sqlQuery .= " Notices.title AS title, "; + $sqlQuery .= " Notices.author AS author, "; + $sqlQuery .= " LTRIM(RTRIM(Notices.NoticeNr)) AS code, "; + $sqlQuery .= " Codes.TextFre AS type, "; + $sqlQuery .= " convert(varchar, CreationDate, 102) as date, "; + $sqlQuery .= " rank() OVER ( "; + $sqlQuery .= " PARTITION BY Codes.TextFre "; + $sqlQuery .= " ORDER BY cast(LTRIM(RTRIM(Notices.NoticeNr)) as int) DESC "; + $sqlQuery .= " ) AS num "; + $sqlQuery .= " FROM Notices "; + + $sqlQuery .= " INNER JOIN Codes "; + $sqlQuery .= " ON Notices.MediaType2Code = Codes.Code "; + $sqlQuery .= " WHERE Codes.Type = 2 "; + if (strlen($type)) + $sqlQuery .= " AND Codes.TextFre = " . $type . " "; + + $sqlQuery .= " AND Notices.NoticeNr NOT LIKE '%~%' "; + $sqlQuery .= " AND Notices.NoticeNr NOT LIKE '%V%' "; + $sqlQuery .= " AND Notices.NoticeNr NOT LIKE '%T%' "; + $sqlQuery .= " AND Notices.MediaType1Code = 'CDD' "; + $sqlQuery .= " AND Notices.Visible = 1 "; + + $sqlQuery .= " GROUP BY Codes.TextFre, "; + $sqlQuery .= " Notices.NoticeNr, "; + $sqlQuery .= " Notices.NoticeId, "; + $sqlQuery .= " Notices.title, "; + $sqlQuery .= " Notices.author, "; + $sqlQuery .= " Notices.CreationDate "; + + $sqlQuery .= ") "; + $sqlQuery .= "SELECT id, title, author, code, type, 0 as score, date "; + $sqlQuery .= "FROM cte "; + $sqlQuery .= "WHERE num <= " . intval($itemsByGroup) . " "; + $sqlQuery .= "ORDER BY date DESC;"; + } + + $resultSet = Connection::execute($sqlQuery); + $result = array(); + while (($row = $resultSet->next())) + $result[] = new AudioBook($row, TRUE); + + return $result; + } + + /** + * 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 categories + */ + public static function listOfCategories() + { + $sql = "SELECT + LTRIM(RTRIM(Code)) as code, + TextFre AS text + FROM Codes + WHERE + type=2 + AND Code!='-' + ORDER BY TextFre;"; + + $results = Connection::execute($sql); + return array_map(function($row) { + return array( + 'code' => $row['code'], + 'text' => $row['text'], + ); + }, $results->to_array()); + } + + /** + * Retrieve the list of all type available in the database. + */ + public static function listOfTypes() + { + $sql = "SELECT DISTINCT + Codes.TextFre AS type + 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); + $results = array_map(function($r) { + return $r['type']; + }, $results->to_array()); + array_unshift($results, "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 to_array() + { + if (!$this->loaded) { + $this->loadDetails(); + } + return parent::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/BookSearch.php b/lib/BookSearch.php new file mode 100644 index 0000000..22092bd --- /dev/null +++ b/lib/BookSearch.php @@ -0,0 +1,78 @@ + Configuration::get('solr.server'), + 'port' => Configuration::get('solr.port'), + 'login' => Configuration::get('solr.username'), + 'password' => Configuration::get('solr.password'), + ); + + $this->client = new SolrClient($options); + $this->query = new SolrQuery(); + $this->query->setQuery('*:*'); + $this->query->addField('id'); + $this->query->addField('code'); + $this->query->addParam('q.op', 'AND'); + } + + public function addQuery($queryText, $queryField = '') + { + if ($queryField != '') + $queryText = "$queryField:($queryText)"; + + $this->queryParts[] = $queryText; + } + + public function addResultField($field) + { + $this->query->addField($field); + } + + public function addSortField($field, $order = SolrQuery::ORDER_DESC) + { + $this->query->addSortField($field, $order); + } + + public function addFacet($field, $minCount = 2) + { + $this->query->setFacet(true); + $this->query->addFacetField($field); + $this->query->setFacetMinCount($minCount, $field); + } + + public function setYearInterval($beginning = 0, $end = 2500) + { + } + + /** + * @param int $start + * @param int $count + * @return SolrObject + */ + 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); + + return $this->client->query($this->query)->getResponse(); + } +} diff --git a/lib/Connection.php b/lib/Connection.php new file mode 100644 index 0000000..a188a08 --- /dev/null +++ b/lib/Connection.php @@ -0,0 +1,205 @@ +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;Database=%s;", + Configuration::get('db.driver'), + Configuration::get('db.server'), + 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; + } + + public static function escape($data) + { + if (is_numeric($data)) + return $data; + $unpacked = unpack('H*hex', $data); + return '0x' . $unpacked['hex']; + } + + final private function __clone() {} +} + +class SqlException extends Exception +{ + private $query; + + public function __construct($message = "Sql Error", $query = "") + { + $this->query = $query; + parent::__construct($message, 0); + } + + public function getSqlError() + { + return $this->getMessage().' while executing: '.$this->query; + } +} + +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/DbMapping.php b/lib/DbMapping.php new file mode 100644 index 0000000..b6973d7 --- /dev/null +++ b/lib/DbMapping.php @@ -0,0 +1,136 @@ +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 + * + * If the name start with sql_, escape the string before to return it to avoid SQL injection. + * @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; + } + + public function reload() + { + $this->setAttributes(DbMapping::find($this->id)->toArray()); + } + + /** + * Function to retrieve data from an id. + * @param int $id + * @return DbMapping + */ + abstract public static function find($id); + + /** + * 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; + } + +} + + +/** + * Exception raised when an invalid attribute name is accessed + */ +class InvalidAttributeException extends Exception +{ + +} diff --git a/lib/User.php b/lib/User.php new file mode 100644 index 0000000..2436250 --- /dev/null +++ b/lib/User.php @@ -0,0 +1,282 @@ + 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; + } + + public function __toString() + { + return $this->displayName; + } + + /** + * Update the database. Note that new user insertion don't work in this implementation. + */ + public function save() + { + $strSQL = "UPDATE " . User::$tableName . " SET FirstName = '$this->sql_firstName', LastName = '$this->sql_lastName', "; + $strSQL .= "DisplayName = '$this->sql_displayName'"; + $strSQL .= "WHERE Replace(UseraccountNr, ' ', '') = '$this->sql_login'"; + Connection::execute($strSQL, true); + + $strSQL = "UPDATE " . User::$addressTableName . " SET Email = '$this->sql_mail', TelephoneMobile = '$this->sql_mobilePhone', "; + $strSQL .= "Telephone = '$this->sql_officePhone', TelephonePrivate = '$this->sql_privatePhone' "; + $strSQL .= "WHERE " . User::$addressTableName . "." . User::$addressIdColumn . " = $this->sql_addressId"; + Connection::execute($strSQL, true); + + if ($this->password) { + $strSQL = "UPDATE " . User::$tableName . " SET Password = UPPER('$this->sql_password') "; + $strSQL .= "WHERE Replace(UseraccountNr, ' ', '') = '$this->sql_login'"; + Connection::execute($strSQL, true); + } + } + + + public function reload() + { + $this->setAttributes(User::find($this->login)->toArray()); + } + + public function getCirculations() + { + if (!$this->circulations) { + $strSQL = "SELECT NoticeId, CheckOutDate, ItemNr FROM Circulations, Items " . + "WHERE Circulations.UseraccountId = $this->id and Items.ItemId=Circulations.ItemId " . + "ORDER BY ItemNr asc"; + + $result = Connection::execute($strSQL); + + $ids = array(); + $checkOutDates = array(); + $itemNrs = array(); + + while ($row = $result->next()) { + $ids[] = $row['NoticeId']; + $checkOutDates[] = $row['CheckOutDate']; + $itemNrs[] = $row['ItemNr']; + } + $this->circulations = AudioBook::find($ids); + + // ici je remplace le champs date du livre par la date du prêt + $counter = 0; + foreach ($this->circulations as &$circulation) { + + $circulation->date = substr($checkOutDates[$counter], 0, 10); + $circulation->itemNr = $itemNrs[$counter]; + + $counter++; + } + } + return $this->circulations; + } + + public function getOldCirculations() + { + + //if(!$this->oldCirculations){ + $strSQL = "SELECT NoticeId, CheckOutDate FROM OldCirculations, Items " . + "WHERE OldCirculations.UseraccountId = $this->id and Items.ItemId=OldCirculations.ItemId " . + "ORDER BY CheckOutDate desc"; + + + $result = Connection::execute($strSQL); + $ids = array(); + $checkOutDates = array(); + while ($row = $result->next()) { + $ids[] = $row['NoticeId']; + $checkOutDates[] = $row['CheckOutDate']; + } + $this->oldCirculations = AudioBook::find($ids); + + // ici je remplace le champs date du livre par la date du prêt + $counter = 0; + foreach ($this->oldCirculations as &$circulation) { + + $circulation->date = substr($checkOutDates[$counter], 0, 10); + $counter++; + } + + //} + + return $this->oldCirculations; + } + + /** + * Add a book to the wish list if it is not already inside. + * + * delete the wishes cache for it to be reloaded the next time getWishes will be called. + * @param int $noticeId + * @return bool + */ + public function addWish($noticeId) + { + $noticeId = str_replace("'", "''", $noticeId); + if (!$this->hasWish($noticeId)) { + // recover last id + $idSQL = "SELECT WishID from Counters"; + $idResult = Connection::execute($idSQL, true); + // return print_r($idResult, 1); + if ($row = $idResult->next()) { + // get new value + $newWishID = $row['WishID'] + 1; + + // update counter + $idSQL = "UPDATE Counters SET WishID=" . $newWishID; + Connection::execute($idSQL, true); + + $table = User::$wishTableName; + $employee_id = Configuration::get('www_employee_id'); + $library_id = Configuration::get('www_library_id'); + $strSQL = "INSERT INTO $table (WishID, " . AudioBook::$idColumn . ", " . User::$idColumn . ", CreationDate, EmployeeID, BranchOfficeID, Remark, ModificationDate)"; + $strSQL .= " VALUES($newWishID, $noticeId, $this->id, GETDATE(), $employee_id, $library_id, '', GETDATE())"; + + // return $strSQL; + Connection::execute($strSQL); + + // $this->wishes = NULL; + return true; + } else { + return false; + } + } + return false; + } + + /** + * Return true if the book is in the wish list + * @param int $noticeId + * @return bool + */ + public function hasWish($noticeId) + { + foreach ($this->getWishes() as $book) { + if ($book->id == $noticeId) { + return true; + } + } + return false; + } + + /** + * Wishes are all the books that this user want to read. + * @param int $limit + * @return AudioBook[] + */ + public function getWishes($limit = 50) + { + if (!$this->wishes) { + $strSQL = "SELECT TOP " . $limit . AudioBook::$idColumn . " FROM " . User::$wishTableName . " WHERE " . User::$idColumn . " = $this->id ORDER BY CreationDate desc"; + + $result = Connection::execute($strSQL); + $ids = array(); + while ($row = $result->next()) { + $ids[] = $row['NoticeID']; + } + $this->wishes = AudioBook::find($ids); + } + return $this->wishes; + } + + /** + * Remove a book from the wish list + * @param int $noticeId + */ + public function deleteWish($noticeId) + { + $noticeId = str_replace("'", "''", $noticeId); + $table = User::$wishTableName; + $strSQL = "DELETE FROM $table"; + $strSQL .= " WHERE " . AudioBook::$idColumn . " = $noticeId AND " . User::$idColumn . " = $this->id;"; + Connection::execute($strSQL, true); + } +} \ No newline at end of file diff --git a/mobile.netbiblio.php b/mobile.netbiblio.php new file mode 100644 index 0000000..d55dd84 --- /dev/null +++ b/mobile.netbiblio.php @@ -0,0 +1,402 @@ +data = array(); + + $client = str_replace("'", "", $client); + $login = str_replace("'", "", $login); + $code = ltrim(str_replace("'", "", $code), '0'); + $itemNr = $code . 'V'; + $itemId = ''; + $userId = ''; + + /* Récupération de l'id de l'exemplaire */ + $sql = "SELECT itemID from Netbiblio3.dbo.items where ltrim(rtrim(itemnr))='$itemNr';"; + $result = Connection::execute($sql, false); + + if ($row = $result->next()) { + $itemId = $row['itemID']; + } + + /* Récupération de l'id du compte */ + $sql = "SELECT useraccountID from Netbiblio3.dbo.UserAccounts where ltrim(rtrim(useraccountnr))='$login';"; + $result = Connection::execute($sql, false); + if ($row = $result->next()) { + $userId = $row['useraccountID']; + } + + $sql = "SELECT circulationId from Netbiblio3.dbo.OldCirculations where useraccountID=$userId AND itemID=$itemId AND ltrim(rtrim(remark))='$client';"; + $result = Connection::execute($sql, false); + + if ($existingEntry = $result->next()) { + $existingId = $existingEntry['circulationId']; + $logSql = "UPDATE OldCirculations SET CheckInDate=GETDATE(), CheckOutDate=GETDATE() WHERE circulationID=$existingId"; + } else { + $sql = "SELECT TOP 1 circulationID FROM oldcirculations ORDER BY CirculationID DESC"; + $result = Connection::execute($sql, false); + if ($row = $result->next()) { + $nextId = $row['circulationID'] + 1; + } else { + $nextId = 1; + } + + /* Ajout d'un ancien prêt dans OldCirculations */ + $worker_id = Configuration::get('netbiblio_worker_id'); + $logSql = "INSERT INTO Netbiblio3.dbo.OldCirculations (" . + " CirculationID, " . + " ItemID, " . + " UseraccountID, " . + " DueDate, " . + " Remark, " . + " CheckOutDate, " . + " CheckOutBranchofficeID, " . + " CheckOutEmployeeID, " . + " CheckInDate, " . + " CheckInBranchofficeID, " . + " CheckInEmployeeID, " . + " Reminders, " . + " Renewals, " . + " Prereminder, " . + " InfoCode, " . + " CheckOutSIP2Info, " . + " CheckInSIP2Info " . + ") VALUES ( " . + " $nextId, " . + " $itemId, " . + " $userId, " . + " DATEADD(month, 2, GETDATE()), " . + " '$client', " . + " GETDATE(), " . + " 2, " . + " $worker_id, " . + " GETDATE(), " . + " 2, " . + " $worker_id, " . + " 0, " . + " 0, " . + " 1, " . + " '-', " . + " 1, " . + " 1 " . + ");"; + + /* Incrément du compteur de prêts "Circulations" dans Items (exemplaires) */ + $incrementUserCountersSQL = + "UPDATE Useraccounts " . + "SET Circulations=Circulations+1, TotalCirculations=TotalCirculations+1 " . + "WHERE UseraccountID=$userId;"; + Connection::execute($incrementUserCountersSQL); + + /* Incrément du compteur de prêts "TotalCirculations" dans UserAccounts (comptes auditeurs) */ + $incrementItemCountersSQL = + "UPDATE Items " . + "SET Circulations=Circulations+1, TotalCirculations=TotalCirculations+1 " . + "WHERE ItemID=$itemId;"; + Connection::execute($incrementItemCountersSQL); + } + Connection::execute($logSql); + } + + 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->data = $user->toArray(); + $this->login = $login; + $this->client = $client; + } + + public function Disconnect() + { + $this->data = array(); + $_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"]); + } + } + + public function IsAuthenticated() + { + $this->data = $this->getUser()->toArray(); + } + + /** + * Adds entries to OldCirculations in Netbiblio database and increments counters on items and useraccounts tables + * For now, keeps a separate log in BSRDownload Database to store IPs + * In case a download has already been logged, only the date of the existing entry is updated, no counter incremented. + * @param string $login + * @return User + * @throws WebException in case the login cannot be found in the database + */ + 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 CheckSession($login = null, $client = 'website') + { + if (!isset ($_SESSION["user"]["login"])) { + return; + } + + if (!$login) { + $login = $_SESSION["user"]["login"]; + } else if ($_SESSION["user"]["login"] !== $login) { + throw new WebException ("CheckSessionBadAuth", "bad authentication", -1001); + } + + $this->login = $login; + $this->client = $client; + } + + public function FindAccount($login) + { + $this->data = $this->getUser($login)->toArray(); + } + + public function GetWishes() + { + $books = $this->getUser()->getWishes(); + $this->data = array_map(array($this, 'AddFiles'), $books); + } + + public function GetCirculations() + { + $circulations = $this->getUser()->getCirculations(); + $this->data = array_map(array($this, 'AddFiles'), $circulations); + } + + public function GetOldCirculations() + { + $circulations = $this->getUser()->getOldCirculations(); + $this->data = array_map(array($this, 'AddFiles'), $circulations); + } + + public function AddWish($bookNr) + { + $bookNr = intval($bookNr); + $bookId = AudioBook::findIdByCode($bookNr); + $this->data[] = $this->getUser()->addWish($bookId); + } + + public function DeleteWish($bookNr) + { + $bookNr = intval($bookNr); + $bookId = AudioBook::findIdByCode($bookNr); + $this->getUser()->deleteWish($bookId); + } + + public function FindBooks($codes) + { + $this->CheckSession(); + + $codeList = array_map('intval', json_decode($codes, true)); + foreach ($codeList as $code) { + if ($code != 0) { + $id = AudioBook::findIdByCode($code); + $this->data[] = $this->AddFiles(AudioBook::find($id)); + } + } + } + + private function AddFiles(AudioBook $book) + { + $book = $book->toArray(); + + $uri = sprintf("%s%s", + Configuration::get('checkfile_url'), + http_build_query(array( + "client" => $this->client, + "login" => $this->login, + "book" => intval($book['code']) + )) + ); + + $ch = curl_init($uri); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, 0); + $json = curl_exec($ch); + curl_close($ch); + + $files = json_decode($json, true); + if (is_array($files)) { + $book['files'] = $files; + } + return $book; + } + + public function FindBook($code) + { + $this->CheckSession(); + + $code = intval($code); + $id = AudioBook::findIdByCode($code); + $this->data = $this->AddFiles(AudioBook::find($id)); + } + + public function Search($query, $start, $limit) + { + $query = array( + 'queryText' => $query, + 'count' => $limit, + 'page' => ($start / $limit), + ); + $this->NewSearch(json_encode($query)); + } + + 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); + } + + $bs = new BookSearch(); + + if (isset($queryArray['queryType'])) { + $bs->addSortField('author_s', SolrQuery::ORDER_ASC); + $bs->addSortField('title_s', SolrQuery::ORDER_ASC); + $bs->addSortField('producer'); + $bs->addSortField('mediatype', SolrQuery::ORDER_ASC); + } else { + $bs->addSortField('availabilitydate'); + $bs->addSortField('author_s', SolrQuery::ORDER_ASC); + $bs->addSortField('title_s', SolrQuery::ORDER_ASC); + } + + if (isset($queryArray['queryText']) && strlen($queryArray['queryText']) > 0) { + $bs->addQuery($queryArray['queryText'], $queryArray['queryType']); + } + + if(isset($queryArray['reader']) && strlen($queryArray['reader']) > 0) { + $bs->addQuery($queryArray['reader'], 'reader'); + } + + if(isset($queryArray['category']) && is_array($queryArray['category'])) { + $selectedCategories = array_filter($queryArray['category'], function ($c) { + return $c != '0'; + }); + if (count($selectedCategories) > 0) { + $selectedCategories = array_map(function ($c) { + return "categorycode: $c"; + }, $selectedCategories); + $bs->addQuery('(' . implode(' OR ', $selectedCategories) . ')'); + } + } + + if(isset($queryArray['producer']) && strlen($queryArray['producer']) > 0) { + $bs->addQuery($queryArray['producer'], 'producer'); + } + + if(isset($queryArray['jeunesse']) && $queryArray['jeunesse']['filtrer'] === 'filtrer') { + $bs->addQuery(1, 'jeunesse'); + } + + $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); + + $this->data['count'] = $results['response']['numFound']; + $this->data['facets'] = $results['facet_counts']['facet_fields']; + + foreach ($results['response']['docs'] as $doc) { + $book = AudioBook::find($doc['id']); + if($book) { + $this->data[] = $this->AddFiles($book); + } + } + } + + public function ListOfReaders() + { + $this->data = AudioBook::listOfReaders(); + } + + public function ListOfCategories() + { + $this->data = AudioBook::listOfCategories(); + } + + public function ListOfTypes() + { + $this->data = array_filter(AudioBook::listOfTypes(), function ($t) { + return strlen($t) > 0; + }); + } + + public function LastBooksByType($type, $itemsByGroup) + { + $this->checkSession(); + + $books = AudioBook::lastBooksByType($type, $itemsByGroup); + $books = array_map(array($this, 'AddFiles'), $books); + foreach ($books as $book) { + $this->data[$book['type']][] = $book; + } + } + + public function InReadingBooks() + { + $this->data = AudioBook::inReading(); + } + + protected function Output() + { + return $this->data; + } +} diff --git a/mobile.php b/mobile.php new file mode 100644 index 0000000..e60679a --- /dev/null +++ b/mobile.php @@ -0,0 +1,6 @@ +Run(); diff --git a/mobile.webservice.php b/mobile.webservice.php new file mode 100644 index 0000000..4385bd5 --- /dev/null +++ b/mobile.webservice.php @@ -0,0 +1,109 @@ +excname = $name; + parent::__construct($reason, $code); + } + + public function getName() + { + return $this->excname; + } +} + +abstract class WebService +{ + private $func = null; + + private $log = ''; + + public function log($message, $withTime = false) { + if($withTime) { + $message = date("d-m-Y h:m:s").' '.$message; + } + + $this->log .= $message."\n"; + } + + public function Run() + { + $this->log("------------------"); + $this->log("Start request", true); + $data = array(); + + try { + $this->Call(); + $data["result"][$this->func] = $this->Output(); + } catch (WebException $e) { + $data["error"]["code"] = $e->getCode(); + $data["error"]["name"] = $e->getName(); + $data["error"]["reason"] = $e->getMessage(); + } + + $this->Send($data); + + $this->log("Request finished", true); + $this->log("------------------\n\n"); + + if(Configuration::get('log.active')) { + 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 ($nbArgs != $nbParams && $nbArgsFix != $nbParams) { + throw new WebException ("CallArgNumber", "you must provide " . $nbArgsFix . " arguments", 4); + } + + $this->log("Calling '".$this->func."'"); + call_user_func_array(array($this, $this->func), $params); + } + + abstract protected function Output(); + + private function Send(array $data) + { + ob_clean(); + flush(); + + $this->log("Data: ".print_r($data, true)); + echo json_encode($data); + } +}