commit cbef1547352a09a8f1d1b8b33c930c93ac295592 Author: Guillermo Dev Date: Wed Oct 10 00:46:53 2018 +0200 initial commit diff --git a/Connection.php b/Connection.php new file mode 100644 index 0000000..be1d6a8 --- /dev/null +++ b/Connection.php @@ -0,0 +1,194 @@ +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 $num_rows; + 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); + $this->num_rows = odbc_num_rows($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 get_num_rows() + { + return $this->num_rows; + } + + 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/DbHelper.php b/DbHelper.php new file mode 100644 index 0000000..abe8eb0 --- /dev/null +++ b/DbHelper.php @@ -0,0 +1,63 @@ +to_array(); + + if($withJeunesse) { + array_unshift($results, array('code' => 'J', 'text' => 'Jeunesse')); + } + + return $results; + } + + /** + * Retrieve the list of all books currently lent 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()); + } +} \ No newline at end of file diff --git a/DbMapping.php b/DbMapping.php new file mode 100644 index 0000000..b78c0ab --- /dev/null +++ b/DbMapping.php @@ -0,0 +1,117 @@ +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 against 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 wishes + + * @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; + } + + /** + * 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/User.php b/User.php new file mode 100644 index 0000000..2e38b6f --- /dev/null +++ b/User.php @@ -0,0 +1,330 @@ + 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, + [UserAccountID] AS id, + REPLACE(UserAccountNr, ' ', '') AS login + FROM [UserAccounts] AS u + LEFT JOIN [Addresses] AS a ON a.[AddressID] = u.[ActualAddressID] + WHERE LTRIM(RTRIM(UserAccountNr)) = '%s' AND disabled = 1 %s AND CategoryCode in ('A', 'M', 'G', 'D', 'I', 'EMP');", + $login, $cond); + + $results = Connection::execute($sql, $raiseError); + return $results->current() !== false ? new User($results->current()) : null; + } + + /** + * Circulations as needed for the BSR internal tools + */ + public function GetCirculations() + { + $sql = sprintf("SELECT + n.NoticeID, + ItemNr, + LTRIM(RTRIM(n.NoticeNr)) AS code, + LTRIM(RTRIM(n.Title)) AS Title, + LTRIM(RTRIM(n.Author)) AS author, + Fields.[300] AS media, + Fields.[901] AS readBy + FROM Circulations AS c + INNER JOIN Items AS i ON i.ItemId = c.ItemId + INNER JOIN Notices AS n ON n.NoticeID = i.NoticeID + LEFT OUTER JOIN ( + SELECT * + FROM ( + SELECT + NoticeID, + Tag AS Field, + NoticeFields.ContentShortPart AS Data + FROM NoticeFields + WHERE Tag IN ('901', '300') + ) AS src + PIVOT ( + MIN(Data) + FOR Field IN ([901], [300]) + ) AS pvt + ) Fields ON n.NoticeID = Fields.NoticeID + WHERE + c.UserAccountID = %s + ORDER BY ItemNr ASC", $this->id); + + $result = Connection::execute($sql); + return $result ? $result->to_array() : array(); + } + + public function GetOldLoansNrs() + { + $sql = sprintf("SELECT + n.NoticeNr + FROM OldCirculations AS c + INNER JOIN Items AS i ON i.ItemId = c.ItemId + INNER JOIN Notices AS n ON n.NoticeID = i.NoticeID + WHERE + c.UserAccountID = %s AND + n.MediaType1Code in ('CDD', 'CDA', 'DVD', 'CDS') AND n.deleted=1 + ORDER BY ItemNr ASC", $this->id); + + $result = Connection::execute($sql); + return $result ? $result->to_array() : array(); + } + + public function getLoansData($table, $sort = "acquisitiondate DESC") + { + $sql = sprintf("SELECT top 50 + realn.NoticeId as NoticeID, + realn.NoticeNr, + CheckOutDate, + c.Remark, + ItemNr + FROM %s AS c + INNER JOIN Items AS i ON i.ItemId = c.ItemId + INNER JOIN Notices AS lentn ON lentn.NoticeID = i.NoticeID + INNER JOIN Notices AS realn ON REPLACE(ltrim(rtrim(lentn.noticenr)), 'V', '') = ltrim(rtrim(realn.noticenr)) + WHERE + c.UserAccountID = %s + ORDER BY %s", $table, $this->id, $sort); + + return Connection::execute($sql)->to_array(); + } + + private function _getLoans($table, $count, $sort) + { + $circulations = $this->getLoansData($table, $sort); + //Logger::log(print_r($circulations, true)); + // getting the intval of the NoticeNr will remove any 'V' or 'T' and thus we will have no issues with + // the virtual books that are used for Downloads and so. + $codes = array_unique(array_map(function($c) { + return trim($c['NoticeNr']); }, $circulations)); + + if($count) { + return count($circulations); + } + + $books = count($codes) > 0 ? BookSearch::GetBooks($codes) : array(); + //Logger::log(print_r($books, true)); + foreach($circulations as $c) { + $id = $c['NoticeID']; + if(isset($books[$id])) { + $books[$id]['checkoutDate'] = $c['CheckOutDate']; + $books[$id]['remark'] = $c['Remark']; + } + } + + return $books; + } + + /** + * Loans (Circulations) as needed on the website + * @param boolean $count return only the count + * @return array + */ + public function GetLoans($count = false) + { + return $this->_getLoans('Circulations', $count, "ItemNr ASC"); + } + + /** + * Old loans (OldCirculations) as needed on the website + * @param boolean $count return only the count + * @return array + */ + public function GetOldLoans($count = false) + { + return $this->_getLoans('OldCirculations', $count, 'CheckOutDate DESC'); + } + + /** + * Books eligible for feedback by the user. Lent or downloaded more than 2 weeks ago and less than 5 monthes. + * @return array + */ + public function GetBooksForFeedback() + { + $sql = sprintf("SELECT n.NoticeNr + FROM OldCirculations AS c + INNER JOIN Items AS i ON i.ItemId = c.ItemId + INNER JOIN Notices AS n ON n.NoticeID = i.NoticeID + WHERE + c.UserAccountID = %s + AND DATEDIFF(month, CheckOutDate, GETDATE()) < 5 + ", $this->id); + + return Connection::execute($sql)->to_array(); } + + /** + * 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 Wishes + (WishID, NoticeID, UserAccountID, CreationDate, EmployeeID, BranchOfficeID, Remark, ModificationDate) + SELECT %s , NoticeID, %s, GETDATE() , %s , %s , '' , GETDATE() + FROM Notices + WHERE LTRIM(RTRIM(NoticeNr)) = '%s';", + $row['WishID'], $this->id, $employee_id, $library_id, $noticeNr); + + $status = Connection::execute($sql); + return $status && ! $status->is_error() && $status->get_num_rows() > 0; + } + + /** + * 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 boolean $count return only the count + * @param int $limit + * @return array + */ + public function getWishes($count = false, $limit = 200) + { + $sql = sprintf("SELECT TOP $limit + NoticeID, CreationDate + FROM Wishes + WHERE UserAccountID = %s + ORDER BY CreationDate DESC", $this->id); + + $result = Connection::execute($sql); + + $wishList = $result->to_array(); + $ids = array_map(function($r) { return $r['NoticeID']; }, $wishList); + if($count) { + return count($ids); + } + + $books = BookSearch::GetBooks($ids, 'id'); + foreach($wishList as $w) { + $id = $w['NoticeID']; + if(isset($books[$id])) { + $books[$id]['creationDate'] = $w['CreationDate']; + } + } + + $creationDates = array(); + foreach ($books as $key => $book) + { + $creationDates[$key] = $book['creationDate']; + } + array_multisort($creationDates, SORT_DESC, $books); + + return $books; + } + + /** + * Remove a book from the wish list + * @param string $noticeNr + * @return boolean Was the deletion was successful or not ? + */ + public function deleteWish($noticeNr) + { + $sql = sprintf("DELETE w + FROM Wishes AS w + INNER JOIN Notices AS n ON n.NoticeID = w.NoticeID + WHERE + LTRIM(RTRIM(n.NoticeNr)) = '%s' + AND UserAccountID = %s;", $noticeNr, $this->id); + $status = Connection::execute($sql, true); + return $status && ! $status->is_error() && $status->get_num_rows() > 0; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9b39359 --- /dev/null +++ b/composer.json @@ -0,0 +1,33 @@ +{ + "name" : "bsr/callioapi", + "description" : "Implementation of bsr/webservice as Callioplayer server api", + "repositories" : [ + { + "type" : "vcs", + "url" : "https://usrpath@bitbucket.org/usrpath/webservice.git" + }, + { + "type" : "vcs", + "url" : "https://usrpath@bitbucket.org/usrpath/bsrdb.git" + } + ], + "authors" : [ + { + "name" : "Simon" + }, + { + "name" : "Guillermo" + } + ], + "require" : { + "bsr/webservice" : "self.version", + "bsr/db" : "self.version" + }, + "autoload": { + "psr-4": {"BSR\\CallioApi\\" : "src/"} + }, + "require-dev": { + "pds/skeleton": "^1.0", + "phpunit/phpunit": "^6" + } +}