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(); } /** * 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 = null) { if (!isset ($_SESSION["user"]["login"])) { return; } if(!$client) { $client = isset($_SESSION["user"]["client"]) ? $_SESSION["user"]["client"] : 'website'; } 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) { 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, true); $codes = array_map('intval', $codes); $books = AudioBook::findBy('NoticeNr', $codes, true); return array_values($this->AddBookData($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 date if we have an availabilityDate for Mobile apps compatibility if(! isset($b['date']) && isset($b['availabilityDate'])) { $b['date'] = date('Y.m.d', strtotime($b['availabilityDate'])); } 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 function FindBook($code) { $this->CheckSession(); $code = intval($code); $book = AudioBook::findBy('NoticeNr', $code, true); return $this->AddBookData($book); } 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 $results['books'] ? $this->AddBookData($results['books']) : array(); } 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'], ); if($results['books']) { $data = array_merge($data, $this->AddBookData($results['books'])); } return $data; } public function ListOfReaders() { return AudioBook::listOfReaders(); } public function ListOfGenres() { return AudioBook::ListOfGenres(); } public function ListOfCategories() { // this method exists for compatibility purpose with the Android and iOS applications return $this->ListOfGenres(); } public function ListOfTypes() { return array_filter(AudioBook::listOfTypes(), function ($t) { return strlen($t) > 0; }); } public function InReadingBooks() { return AudioBook::inReading(); } public function LastBooksByType($type, $itemsByGroup) { $this->CheckSession(); $s = new BookSearch(); if($type == 'Jeunesse') { $s->addQuery(1, 'jeunesse'); } else { $s->addQuery($type, 'genre'); } $s->addSortField('availabilityDate'); try { $results = $s->getResults(0, $itemsByGroup); } catch(\SolrClientException $e) { throw new WebException ("SolrError", $e->getMessage(), -710); } $ids = array_map(function($r) { return $r['id']; }, $results['response']['docs']); $books = AudioBook::findBy('NoticeID', $ids, true); $books = $this->AddBookData($books); $data = array(); foreach($books as $b) { $data[$b['type']][] = $b; } return $data; } }