You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
986 lines
31 KiB
PHP
986 lines
31 KiB
PHP
<?php
|
|
|
|
namespace BSR;
|
|
|
|
use BSR\Lib\Configuration;
|
|
use BSR\Lib\db\DBHelper;
|
|
use BSR\Lib\db\Connection;
|
|
use BSR\Lib\db\User;
|
|
use BSR\Lib\Exception\AuthenticationException;
|
|
use BSR\Lib\Exception\WebException;
|
|
use BSR\Lib\Search\BookSearch;
|
|
use BSR\Lib\WebService;
|
|
use BSR\Lib\Logger;
|
|
|
|
class NetBiblio extends WebService
|
|
{
|
|
/** @var string $version version number */
|
|
public static $version = '1.2.0';
|
|
private $login = '';
|
|
private $client = 'website';
|
|
|
|
public function __construct() {
|
|
parent::__construct(self::$version);
|
|
}
|
|
|
|
/**
|
|
* Set the current login and client based on information
|
|
* from the session.
|
|
*/
|
|
private function CheckSession()
|
|
{
|
|
if (! isset ($_SESSION["user"]["login"]) || empty($_SESSION["user"]["login"])) {
|
|
return;
|
|
}
|
|
|
|
$this->login = $_SESSION["user"]["login"];
|
|
$this->client = isset($_SESSION["user"]["client"]) ? $_SESSION["user"]["client"] : 'website';
|
|
}
|
|
|
|
/**
|
|
* Retrieve information about the current user from the database.
|
|
* If a username is given, first validate that it is the same
|
|
* currently in the session.
|
|
*
|
|
* @param string|null $login
|
|
* @return User
|
|
* @throws AuthenticationException
|
|
*/
|
|
private function getUser($login = null)
|
|
{
|
|
if(! is_null($login) && $_SESSION["user"]["login"] !== $login) {
|
|
throw new AuthenticationException("BadLogin", "Login '$login' is invalid.", AuthenticationException::BAD_LOGIN);
|
|
}
|
|
|
|
$this->checkSession();
|
|
|
|
if(strlen($this->login) == 0) {
|
|
throw new AuthenticationException("LoginEmpty", "No login information found in session.", AuthenticationException::LOGIN_EMPTY);
|
|
}
|
|
|
|
$user = User::find($this->login);
|
|
|
|
if (!$user) {
|
|
throw new AuthenticationException("UserNotFound", "No user found for '{$this->login}'.", AuthenticationException::USER_NOT_FOUND);
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Retrieve file information (samples and zip) for a list of books based
|
|
* on their code (NoticeNr).
|
|
* This should be called only if those information are not already in Solr
|
|
* for the given books.
|
|
*
|
|
* @param array $codes
|
|
* @return array File information indexed by book code
|
|
*/
|
|
private function GetFiles(array $codes)
|
|
{
|
|
$codes = array_map('intval', $codes);
|
|
|
|
|
|
$uri = sprintf("%s%s",
|
|
Configuration::get('checkfile_url'),
|
|
http_build_query(array("book" => implode(',', $codes)))
|
|
);
|
|
// Logger::log($uri);
|
|
$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);
|
|
}
|
|
|
|
/**
|
|
* Save files information (samples and zip) for books into Solr
|
|
* based on their id (NoticeId).
|
|
*
|
|
* @param $files
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Add some information to each books :
|
|
* 1° File information if not already their (@see GetFiles),
|
|
* those information are then saved into Solr (@see SetFiles)
|
|
* 2° Correctly set hash on zip file to authenticate download
|
|
* 3° Compatibility fields for mobile apps
|
|
*
|
|
* You can pass either a single book or an array of books.
|
|
*
|
|
* @param array $books either one or a list of books
|
|
* @return array either one or a list of books
|
|
*/
|
|
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;
|
|
}
|
|
|
|
// date will be already set if we are updating Circulations
|
|
if(! isset($b['date'])) {
|
|
$b['date'] = date('Y.m.d', strtotime($b['availabilityDate']));
|
|
}
|
|
|
|
// add fields for mobile apps compatibility
|
|
$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 .wav and ogg)
|
|
isset($b['files']['zip']) // we want a zip file
|
|
);
|
|
});
|
|
|
|
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);
|
|
}
|
|
|
|
// retrieve login information if they exists to generate the URL
|
|
$this->CheckSession();
|
|
|
|
// add hash, client and login into zip file uri
|
|
$books = array_map(function($b) {
|
|
$b['files']['cacheable_uri'] = isset($b['files']['zip']['uri']) ? $b['files']['zip']['uri'] : false;
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Return true if the value matches a book code (a number with 1 to 5 digits)
|
|
* @param $val
|
|
* @return int
|
|
*/
|
|
protected function IsBookCode($val) {
|
|
return preg_match('/^[0-9]{1,5}$/', $val);
|
|
}
|
|
|
|
// **********************************
|
|
// * Public methods *
|
|
// **********************************
|
|
|
|
/**
|
|
* This method register a download made through the website or mobile application
|
|
* as a lent and returned book (OldCirculation).
|
|
*
|
|
* @param string $client
|
|
* @param string $login
|
|
* @param int $code
|
|
* @return array
|
|
* @throws Lib\Exception\SqlException
|
|
* @throws WebException
|
|
*/
|
|
public function AddDownloadLog($client, $login, $code)
|
|
{
|
|
$dl_alert = 80;
|
|
$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, DisplayName FROM UserAccounts WHERE LTRIM(RTRIM(UserAccountNr)) = '$login';";
|
|
$result = Connection::execute($sql, false);
|
|
if ($row = $result->current()) {
|
|
$userId = $row['UserAccountID'];
|
|
$username = $row['DisplayName'];
|
|
} 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 array('success' => 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 = "SELECT count(*) as DLs FROM OldCirculations WHERE UserAccountID = $userId
|
|
AND checkoutdate >= Dateadd(month, Datediff(month, 0, Getdate()), 0);";
|
|
|
|
$result = Connection::execute($sql, false);
|
|
|
|
if ($row = $result->current()) {
|
|
$DLs = $row['DLs'];
|
|
if($DLs >= $dl_alert){
|
|
|
|
$to = 'g@lespagesweb.ch';
|
|
$subject = 'Limite atteinte pour '.$username.", ".$login;
|
|
$message = "Nombre de livres ce mois: ".($DLs+1);
|
|
$headers = 'From: webmaster@abage.ch' . "\r\n" .
|
|
'Reply-To: webmaster@abage.ch' . "\r\n" .
|
|
'X-Mailer: PHP/' . phpversion();
|
|
|
|
mail($to, $subject, $message, $headers);
|
|
}
|
|
}
|
|
|
|
$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
|
|
);";
|
|
$status = Connection::execute($sql);
|
|
return array('success' => $status && ! $status->is_error() && $status->get_num_rows() > 0);
|
|
}
|
|
|
|
/**
|
|
* This method authenticates and store the login information into the session so
|
|
* that the next calls can be made for this user.
|
|
*
|
|
* @param $login
|
|
* @param $password
|
|
* @param string $client
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function Authenticate($login, $password, $client = "website")
|
|
{
|
|
session_unset(); /* destroy all session vars */
|
|
|
|
$user = User::authenticate($login, $password);
|
|
|
|
if (! $user) {
|
|
throw new AuthenticationException("AuthenticationFailed", "Invalid login or password.", AuthenticationException::AUTHENTICATION_FAILED);
|
|
}
|
|
|
|
$_SESSION["user"]["login"] = $login;
|
|
$_SESSION["user"]["client"] = $client;
|
|
|
|
$this->login = $login;
|
|
$this->client = $client;
|
|
|
|
return $user->toArray();
|
|
}
|
|
|
|
/**
|
|
* This method disconnects the current user and clear all session data.
|
|
*
|
|
* @return array
|
|
*/
|
|
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('success' => true);
|
|
}
|
|
|
|
/**
|
|
* Return the information associated with the currently authenticated user
|
|
* if any.
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function IsAuthenticated()
|
|
{
|
|
return $this->getUser()->toArray();
|
|
}
|
|
|
|
/**
|
|
* This method returns the account information for the given login, but only
|
|
* if it matches the currently authenticated user.
|
|
*
|
|
* @param string $login
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function FindAccount($login)
|
|
{
|
|
return $this->getUser($login)->toArray();
|
|
}
|
|
|
|
/**
|
|
* This method returns the books on the currently authenticated user wishlist.
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function GetWishes()
|
|
{
|
|
$books = $this->getUser()->getWishes();
|
|
return array_values($this->AddBookData($books));
|
|
}
|
|
|
|
/**
|
|
* This method returns the list of all Circulations for the currently
|
|
* authenticated user in a format suited for BSR internal tools
|
|
* (CD engraving for example).
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function GetCirculations()
|
|
{
|
|
return $this->getUser()->GetCirculations();
|
|
}
|
|
|
|
/**
|
|
* This method returns the list of books that are currently lent to the
|
|
* authenticated user.
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function GetLoans()
|
|
{
|
|
$circulations = $this->getUser()->GetLoans();
|
|
return array_values($this->AddBookData($circulations));
|
|
}
|
|
|
|
|
|
/**
|
|
* This method returns the list of books that the currently authenticated user
|
|
* has downloaded or that were lent to him.
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function GetOldLoans()
|
|
{
|
|
$circulations = $this->getUser()->GetOldLoans();
|
|
return $circulations; //array_values($this->AddBookData($circulations));
|
|
}
|
|
|
|
/**
|
|
* This method returns the list of noticenr that
|
|
* were lent to the currently authenticated user.
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function GetOldLoansNrs()
|
|
{
|
|
$circulations = $this->getUser()->GetOldLoansNrs();
|
|
return $circulations;
|
|
}
|
|
|
|
/**
|
|
* This method returns the books lent to the current user in the time period
|
|
* 2 weeks to 4 month.
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function GetBooksForFeedback()
|
|
{
|
|
return array_values($this->getUser()->GetBooksForFeedback());
|
|
}
|
|
|
|
/**
|
|
* This method return information for the user dashboard :
|
|
*
|
|
* ° 'loans' : number of current loans
|
|
* ° 'oldLoans' : number of past loans
|
|
* ° 'wishes' : number of wishes
|
|
* ° 'novelties' : new books
|
|
* ° 'recommendations' : recommended books
|
|
*
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function GetDashboardData()
|
|
{
|
|
$user = $this->getUser();
|
|
return array(
|
|
'loans' => $user->GetLoans(true),
|
|
'oldLoans' => $user->GetOldLoans(true),
|
|
'wishes' => $user->getWishes(true),
|
|
'novelties' => $this->LastBooksByType('', 12),
|
|
'recommendations' => $this->MoreLikeLoans(8),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* This method adds a book to the currently authenticated user wishlist
|
|
* based on its code.
|
|
*
|
|
* @param int $code
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function AddWish($code)
|
|
{
|
|
$status = $this->getUser()->addWish($code);
|
|
return array('success' => $status);
|
|
}
|
|
|
|
/**
|
|
* This method deletes the given book from the currently authenticated
|
|
* user wishlist based on its code.
|
|
*
|
|
* @param int $code
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function DeleteWish($code)
|
|
{
|
|
$status = $this->getUser()->deleteWish($code);
|
|
return array('success' => $status);
|
|
}
|
|
|
|
/**
|
|
* This method returns an array of books based on their code
|
|
*
|
|
* @param array|int[] $codes
|
|
* @return array
|
|
*/
|
|
public function FindBooks($codes)
|
|
{
|
|
return array_values($this->AddBookData(BookSearch::GetBooks(json_decode($codes))));
|
|
}
|
|
|
|
/**
|
|
* This method return a book based on its code
|
|
*
|
|
* @param int $code
|
|
* @return array
|
|
*/
|
|
public function FindBook($code)
|
|
{
|
|
$books = $this->AddBookData(BookSearch::GetBooks(array($code)));
|
|
return reset($books);
|
|
}
|
|
|
|
/**
|
|
* This method returns a list of random books.
|
|
* For a given seed, the list should be always the same at least while
|
|
* Solr is not restarted (or documents re-indexed)
|
|
*
|
|
* @param int $number Number of random books to return
|
|
* @param null $seed
|
|
* @param int $page
|
|
* @return array
|
|
* @throws WebException
|
|
*/
|
|
public function GetRandomBooks($number = 100, $seed = null, $page = 0) {
|
|
if(is_null($seed)) {
|
|
$seed = time();
|
|
}
|
|
|
|
$bs = new BookSearch();
|
|
$bs->addSortField('random_'.$seed);
|
|
$bs->addFilterQuery(1, 'visible');
|
|
$results = $bs->getResults($page * $number, $number);
|
|
return $this->AddBookData($results['books']);
|
|
}
|
|
|
|
/**
|
|
* This method is used by the iOS application to perform searching.
|
|
* If a number is given, search in the book codes, otherwise perform
|
|
* a full text search.
|
|
*
|
|
* @param string $text Text to search
|
|
* @param int $start
|
|
* @param int $limit
|
|
* @return array an array of books
|
|
* @throws WebException
|
|
*/
|
|
public function Search($text, $start, $limit)
|
|
{
|
|
$query = array(
|
|
'queryText' => $text,
|
|
'count' => $limit,
|
|
'page' => max(intval($start) - 1, 0),
|
|
);
|
|
|
|
if($this->IsBookCode($text)) {
|
|
$query['queryType'] = 'code';
|
|
}
|
|
|
|
$data = $this->NewSearch(json_encode($query));
|
|
|
|
// remove fields that are not used in "old" search
|
|
unset($data['count']);
|
|
unset($data['facets']);
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* This method is used by the website and the Android application to perform
|
|
* book search.
|
|
*
|
|
* Search parameters :
|
|
*
|
|
* ° queryText : the text to search for
|
|
* ° queryType : the field to search in, defaults to 'text'
|
|
*
|
|
* ° jeunesse : only display books for kids (must have format 'jeunesse' => array('filtrer' => 'filtrer')
|
|
*
|
|
* ° producerCode : filter by 'producerCode'
|
|
* ° genreCode : filter by 'genreCode'
|
|
* ° author : filter by 'author'
|
|
* ° reader : filter by 'reader'
|
|
* ° motsMatieres : filter by 'motsMatieres'
|
|
*
|
|
* ° duration : exact duration in minutes
|
|
* ° durationMin : minimal duration in minutes
|
|
* ° durationMax : maximal duration in minutes
|
|
*
|
|
* ° count : number of results we want
|
|
* ° page : page to start at (0 is the first)
|
|
*
|
|
* Shortcuts :
|
|
*
|
|
* ° producer : synonym for 'producerCode' (see above)
|
|
* ° genre : synonym for 'genreCode' (see above)
|
|
*
|
|
* Deprecated, but still in use on mobile apps :
|
|
*
|
|
* ° category : synonym for 'genre' (see above)
|
|
*
|
|
* Return value :
|
|
*
|
|
* The return value start with two keys :
|
|
*
|
|
* ° 'count' : which is the total number of available results
|
|
* ° 'facets' : which contains all other relevent information (facets, spellchecking,
|
|
* highlighting, etc). This name is used for compatibility reasons.
|
|
*
|
|
* Then, the books come right after.
|
|
*
|
|
* @param string $values JSON encoded object
|
|
* @return array
|
|
* @throws WebException
|
|
*/
|
|
public function NewSearch($values)
|
|
{
|
|
// we need the client to perform some query field replacement
|
|
$this->CheckSession();
|
|
|
|
$queryArray = json_decode($values, true);
|
|
if(! is_array($queryArray)) {
|
|
throw new WebException("CallArg", "Argument must be valid JSON.", -42);
|
|
}
|
|
|
|
// shortcuts and the iOS app still uses 'category'
|
|
$compatibility = array(
|
|
'genre' => 'genreCode',
|
|
'category' => 'genreCode',
|
|
'producer' => 'producerCode'
|
|
);
|
|
foreach($compatibility as $old => $new) {
|
|
if(isset($queryArray[$old])) {
|
|
$queryArray[$new] = $queryArray[$old];
|
|
unset($queryArray[$old]);
|
|
}
|
|
}
|
|
|
|
$bs = new BookSearch();
|
|
|
|
// when search on a particular field, put results in descending date order
|
|
if(!isset($queryArray['queryText']) && !isset($queryArray['author_fr']) && !isset($queryArray['title_fr']) ) {
|
|
$bs->addSortField('availabilityDate');
|
|
}
|
|
|
|
if (isset($queryArray['queryText']) && strlen($queryArray['queryText']) > 0) {
|
|
$type = isset($queryArray['queryType']) ? $queryArray['queryType'] : null;
|
|
|
|
if(in_array($type, array('title', 'author', 'reader'))) {
|
|
// we don't want an exact search on mobile apps
|
|
$type = $type.'_fr';
|
|
} else if($type == 'text') {
|
|
// The field 'text' is still used by the Android app but does not exists anymore
|
|
// We use the default search fields in this case.
|
|
$type = null;
|
|
}
|
|
|
|
$bs->addQuery($queryArray['queryText'], $type);
|
|
}
|
|
|
|
if(isset($queryArray['genreCode']) && is_array($queryArray['genreCode'])) {
|
|
// Jeunesse is a particular genre with it's own way of being searched
|
|
if(($key = array_search('J', $queryArray['genreCode'])) !== false) {
|
|
unset($queryArray['genreCode']['J']);
|
|
$queryArray['jeunesse'] = array('filtrer' => 'filtrer');
|
|
}
|
|
}
|
|
|
|
if(isset($queryArray['jeunesse']) && $queryArray['jeunesse']['filtrer'] === 'filtrer') {
|
|
$bs->addFilterQuery(1, 'jeunesse');
|
|
}
|
|
|
|
if(isset($queryArray['duration'])) {
|
|
$bs->addQuery($queryArray['duration'], 'duration');
|
|
} else if(isset($queryArray['durationMin']) || isset($queryArray['durationMax'])) {
|
|
$min = isset($queryArray['durationMin']) ? $queryArray['durationMin'] : '*';
|
|
$max = isset($queryArray['durationMax']) ? $queryArray['durationMax'] : '*';
|
|
$bs->addRange('duration', $min, $max);
|
|
}
|
|
|
|
$availableFields = array('producerCode', 'genreCode', 'author', 'author_fr', 'title_fr', 'reader', 'reader_fr', 'motsMatieres', 'mediaType');
|
|
foreach($availableFields as $q) {
|
|
if(isset($queryArray[$q]) && (
|
|
(is_string($queryArray[$q]) && strlen($queryArray[$q]) > 0) ||
|
|
(is_array($queryArray[$q]) && count($queryArray[$q]) > 0)
|
|
)) {
|
|
if(is_array($queryArray[$q])) {
|
|
// Genres cannot overlap, so we use 'OR', otherwise use 'AND'
|
|
$bs->addCompoundQuery($queryArray[$q], $q, $q == 'genreCode' ? 'OR' : 'AND');
|
|
} else {
|
|
if($q == 'reader'){
|
|
$q2 = 'reader_fr';
|
|
$queryArray[$q] = ucfirst($queryArray[$q]);
|
|
} else{$q2 = $q;}
|
|
|
|
$bs->addQuery($queryArray[$q], $q2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// we only want visible books in search results
|
|
$bs->addFilterQuery(1, 'visible');
|
|
|
|
$count = isset($queryArray['count']) ? (int) $queryArray['count'] : Configuration::get('solr.result_count');
|
|
$start = isset($queryArray['page']) ? $queryArray['page'] * $count : 0;
|
|
$facets = isset($queryArray['facets']) && $queryArray['facets'];
|
|
$spellcheck = isset($queryArray['spellcheck']) && $queryArray['spellcheck'];
|
|
$highlight = isset($queryArray['highlight']) && $queryArray['highlight'];
|
|
|
|
$results = $bs->getResults($start, $count, $facets, $spellcheck, $highlight);
|
|
|
|
$data = array(
|
|
'count' => $results['count'],
|
|
'facets' => $results['facets'],
|
|
);
|
|
|
|
$finalResult = array_merge($data, $this->AddBookData($results['books']));
|
|
for($i = 0; $i < count($finalResult)-2 ; $i++){
|
|
$finalResult[$i]['position']=$i;
|
|
}
|
|
return $finalResult;
|
|
}
|
|
|
|
/**
|
|
* This method return books similar to the one given.
|
|
*
|
|
* @param int|array $ids One or multiple book ids
|
|
* @param int number of books
|
|
* @return array
|
|
*/
|
|
public function MoreLikeThis($ids, $number = 8)
|
|
{
|
|
$bs = new BookSearch(false);
|
|
$bs->addOrQuery(is_array($ids) ? $ids : array($ids), 'id');
|
|
$bs->setHandler('more');
|
|
$results = $bs->getResults(0, $number);
|
|
return $this->AddBookData($results['books']);
|
|
}
|
|
|
|
/**
|
|
* This method return books similar to the one given.
|
|
*
|
|
* @param int|array $ids One or multiple book ids
|
|
* @param int number of books
|
|
* @return array
|
|
*/
|
|
public function MoreLikeThisByCode($codes, $number = 8)
|
|
{
|
|
$bs = new BookSearch(false);
|
|
$bs->addOrQuery(is_array($code) ? $codes : array($codes), 'code');
|
|
$bs->setHandler('more');
|
|
$results = $bs->getResults(0, $number);
|
|
return array_values($this->AddBookData($results['books']));
|
|
}
|
|
/**
|
|
* This method returns books similar to the books already
|
|
* loaned by the current user.
|
|
*
|
|
* @param int number of books
|
|
* @return array
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function MoreLikeLoans($number = 8)
|
|
{
|
|
$circulations = $this->getUser()->getLoansData('OldCirculations');
|
|
$ids = array_map(function($c) { return $c['NoticeId']; }, $circulations);
|
|
return $this->MoreLikeThis($ids, $number);
|
|
}
|
|
|
|
/**
|
|
* This method return a list of suggested titles for the given search terms
|
|
* @param string $text
|
|
* @return array
|
|
*/
|
|
public function Suggest($text)
|
|
{
|
|
$bs = new BookSearch();
|
|
return $bs->suggest($text);
|
|
}
|
|
|
|
/**
|
|
* This method returns the list of durations in minutes but with 30 minutes gaps
|
|
* and the count of books in each group.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function ListOfDurations()
|
|
{
|
|
return BookSearch::GetTermsRange('duration');
|
|
}
|
|
|
|
/**
|
|
* This method returns the list of all volunteer readers that read book
|
|
* in the database.
|
|
* @return array
|
|
*/
|
|
public function ListOfReaders()
|
|
{
|
|
$readers = array();
|
|
foreach(BookSearch::GetTerms('reader') as $name => $count) {
|
|
$parts = explode(" ", $name);
|
|
$firstname = array_shift($parts);
|
|
$lastname = implode(" ", $parts);
|
|
|
|
$fullname = trim($lastname.' '.$firstname);
|
|
$readers[$fullname] = array(
|
|
'lastname' => $lastname,
|
|
'firstname' => $firstname,
|
|
'count' => $count,
|
|
);
|
|
}
|
|
|
|
// sort readers by lastname
|
|
ksort($readers);
|
|
|
|
return array_values($readers);
|
|
}
|
|
|
|
/**
|
|
* This method is called by the website in Drupal to get the list
|
|
* of available 'Genres'.
|
|
* @return array
|
|
*/
|
|
public function ListOfGenres()
|
|
{
|
|
return DBHelper::ListOfGenres();
|
|
}
|
|
|
|
/**
|
|
* This method is called by the Android application to get the list
|
|
* of available 'Genres'.
|
|
* @return array
|
|
*/
|
|
public function ListOfCategories()
|
|
{
|
|
return DBHelper::ListOfGenres(true);
|
|
}
|
|
|
|
/**
|
|
* 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'];
|
|
}, DBHelper::ListOfGenres(true));
|
|
}
|
|
|
|
/**
|
|
* This method returns the list of all books that are currently
|
|
* being read by the volunteers.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function InReadingBooks()
|
|
{
|
|
return DBHelper::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 string $genre Genre for which we want books (not the code), this can be empty
|
|
* @param int $number number of books
|
|
* @return array
|
|
* @throws WebException
|
|
*/
|
|
public function LastBooksByType($genre, $number)
|
|
{
|
|
$s = new BookSearch();
|
|
if($genre == 'Jeunesse') {
|
|
$s->addQuery(1, 'jeunesse');
|
|
} else if(! empty($genre)) {
|
|
$s->addQuery($genre, 'genre');
|
|
}
|
|
|
|
$s->addSortField('availabilityDate');
|
|
|
|
// we only want visible books
|
|
$s->addFilterQuery(1, 'visible');
|
|
|
|
$results = $s->getResults(0, $number);
|
|
$books = $this->AddBookData($results['books']);
|
|
|
|
if(empty($genre)) {
|
|
return $books;
|
|
}
|
|
|
|
$data = array();
|
|
foreach($books as $b) {
|
|
$data[$genre][] = $b;
|
|
}
|
|
return $data;
|
|
}
|
|
}
|