diff --git a/.htaccess b/.htaccess index 2d73b14..d20a79d 100644 --- a/.htaccess +++ b/.htaccess @@ -1,3 +1,5 @@ +DirectoryIndex index.php + XSendFile on diff --git a/OPDS_renderer.php b/OPDS_renderer.php index 60f3636..cbd9212 100644 --- a/OPDS_renderer.php +++ b/OPDS_renderer.php @@ -126,15 +126,19 @@ class OPDSRenderer self::renderLink ($link); $link = new LinkNavigation ("?" . $_SERVER['QUERY_STRING'], "self"); self::renderLink ($link); - $urlparam = "?page=" . self::PAGE_OPENSEARCH; + $urlparam = "?"; if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB)); if ($config['cops_generate_invalid_opds_stream'] == 0 || preg_match("/(MantanoReader|FBReader)/", $_SERVER['HTTP_USER_AGENT'])) { // Good and compliant way of handling search + $urlparam = addURLParameter ($urlparam, "page", self::PAGE_OPENSEARCH); $link = new Link ("feed.php" . $urlparam, "application/opensearchdescription+xml", "search", "Search here"); } else { // Bad way, will be removed when OPDS client are fixed + $urlparam = addURLParameter ($urlparam, "query", "{searchTerms}"); + $urlparam = str_replace ("%7B", "{", $urlparam); + $urlparam = str_replace ("%7D", "}", $urlparam); $link = new Link ($config['cops_full_url'] . 'feed.php' . $urlparam, "application/atom+xml", "search", "Search here"); } self::renderLink ($link); diff --git a/base.php b/base.php index 2a410e3..46b7a77 100644 --- a/base.php +++ b/base.php @@ -173,7 +173,7 @@ function localize($phrase, $count=-1, $reset=false) { $translations = json_decode($lang_file_content, true); /* Clean the array of all unfinished translations */ - foreach ($translations as $key => $val) { + foreach (array_keys ($translations) as $key) { if (preg_match ("/^##TODO##/", $key)) { unset ($translations [$key]); } @@ -441,7 +441,7 @@ class Page $database = GetUrlParam (DB); if (is_array ($config['calibre_directory']) && is_null ($database)) { $i = 0; - foreach ($config['calibre_directory'] as $key => $value) { + foreach (array_keys ($config['calibre_directory']) as $key) { $nBooks = Book::getBookCount ($i); array_push ($this->entryArray, new Entry ($key, "cops:{$i}:catalog", str_format (localize ("bookword", $nBooks), $nBooks), "text", @@ -471,7 +471,6 @@ class Page public function isPaginated () { - global $config; return (getCurrentOption ("max_item_per_page") != -1 && $this->totalNumber != -1 && $this->totalNumber > getCurrentOption ("max_item_per_page")); @@ -479,7 +478,6 @@ class Page public function getNextLink () { - global $config; $currentUrl = $_SERVER['QUERY_STRING']; $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . $_SERVER['QUERY_STRING']); if (($this->n) * getCurrentOption ("max_item_per_page") < $this->totalNumber) { @@ -490,7 +488,6 @@ class Page public function getPrevLink () { - global $config; $currentUrl = $_SERVER['QUERY_STRING']; $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . $_SERVER['QUERY_STRING']); if ($this->n > 1) { @@ -501,7 +498,6 @@ class Page public function getMaxPage () { - global $config; return ceil ($this->totalNumber / getCurrentOption ("max_item_per_page")); } @@ -535,8 +531,6 @@ class PageAllAuthorsLetter extends Page { public function InitializeContent () { - global $config; - $this->idPage = Author::getEntryIdByLetter ($this->idGet); $this->entryArray = Author::getAuthorsByStartingLetter ($this->idGet); $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("authorword", count ($this->entryArray)), count ($this->entryArray)), $this->idGet); @@ -729,7 +723,7 @@ class PageQueryResult extends Page $this->entryArray = Author::getAuthorsByStartingLetter ('%' . $this->query); break; case self::SCOPE_TAG : - $this->entryArray = Tag::getAllTagsByQuery ($this->query); + list ($this->entryArray, $this->totalNumber) = Tag::getAllTagsByQuery ($this->query, -1); break; case self::SCOPE_SERIES : $this->entryArray = Serie::getAllSeriesByQuery ($this->query); @@ -856,6 +850,11 @@ abstract class Base const COMPATIBILITY_XML_ALDIKO = "aldiko"; private static $db = NULL; + + public static function isMultipleDatabaseEnabled () { + global $config; + return is_array ($config['calibre_directory']); + } public static function getDbList () { global $config; @@ -908,11 +907,15 @@ abstract class Base self::$db = NULL; } - public static function executeQuery($query, $columns, $filter, $params, $n, $database = NULL) { + public static function executeQuery($query, $columns, $filter, $params, $n, $database = NULL, $numberPerPage = NULL) { global $config; $totalResult = -1; + + if (is_null ($numberPerPage)) { + $numberPerPage = getCurrentOption ("max_item_per_page"); + } - if (getCurrentOption ("max_item_per_page") != -1 && $n != -1) + if ($numberPerPage != -1 && $n != -1) { // First check total number of results $result = self::getDb ($database)->prepare (str_format ($query, "count(*)", $filter)); @@ -921,7 +924,8 @@ abstract class Base // Next modify the query and params $query .= " limit ?, ?"; - array_push ($params, ($n - 1) * getCurrentOption ("max_item_per_page"), getCurrentOption ("max_item_per_page")); + + array_push ($params, ($n - 1) * $numberPerPage, $numberPerPage); } $result = self::getDb ($database)->prepare(str_format ($query, $columns, $filter)); diff --git a/book.php b/book.php index f30f80a..10237e7 100644 --- a/book.php +++ b/book.php @@ -78,7 +78,6 @@ class Book extends Base { public function __construct($line) { - global $config; $this->id = $line->id; $this->title = $line->title; $this->timestamp = strtotime ($line->timestamp); @@ -177,7 +176,6 @@ class Book extends Base { } public function getDetailUrl ($permalink = false) { - global $config; $urlParam = $this->getUri (); if (!is_null (GetUrlParam (DB))) $urlParam = addURLParameter ($urlParam, DB, GetUrlParam (DB)); return 'index.php' . $urlParam; @@ -416,7 +414,6 @@ class Book extends Base { public function getLinkArray () { - global $config; $linkArray = array(); if ($this->hasCover) @@ -454,7 +451,6 @@ class Book extends Base { } public static function getBookCount($database = NULL) { - global $config; $nBooks = parent::getDb ($database)->query('select count(*) from books')->fetchColumn(); return $nBooks; } @@ -553,12 +549,12 @@ order by substr (upper (sort), 1, 1)"); return $entryArray; } - public static function getBooksByStartingLetter($letter, $n) { - return self::getEntryArray (self::SQL_BOOKS_BY_FIRST_LETTER, array ($letter . "%"), $n); + public static function getBooksByStartingLetter($letter, $n, $database = NULL, $numberPerPage = NULL) { + return self::getEntryArray (self::SQL_BOOKS_BY_FIRST_LETTER, array ($letter . "%"), $n, $database, $numberPerPage); } - public static function getEntryArray ($query, $params, $n, $database = NULL) { - list ($totalNumber, $result) = parent::executeQuery ($query, self::BOOK_COLUMNS, self::getFilterString (), $params, $n, $database); + public static function getEntryArray ($query, $params, $n, $database = NULL, $numberPerPage = NULL) { + list ($totalNumber, $result) = parent::executeQuery ($query, self::BOOK_COLUMNS, self::getFilterString (), $params, $n, $database, $numberPerPage); $entryArray = array(); while ($post = $result->fetchObject ()) { @@ -571,7 +567,8 @@ order by substr (upper (sort), 1, 1)"); public static function getAllRecentBooks() { global $config; - list ($entryArray, $totalNumber) = self::getEntryArray (self::SQL_BOOKS_RECENT . $config['cops_recentbooks_limit'], array (), -1); + $entryArray = self::getEntryArray (self::SQL_BOOKS_RECENT . $config['cops_recentbooks_limit'], array (), -1); + $entryArray = $entryArray [0]; return $entryArray; } @@ -582,30 +579,60 @@ function getJson ($complete = false) { $page = getURLParam ("page", Base::PAGE_INDEX); $query = getURLParam ("query"); $search = getURLParam ("search"); + $multi = getURLParam ("multi"); $qid = getURLParam ("id"); $n = getURLParam ("n", "1"); $database = GetUrlParam (DB); if ($search) { $out = array (); - $arrayTag = Tag::getAllTagsByQuery ($query); - $arraySeries = Serie::getAllSeriesByQuery ($query); - $arrayAuthor = Author::getAuthorsByStartingLetter ('%' . $query); - list ($arrayBook, $totalNumber) = Book::getBooksByStartingLetter ('%' . $query, -1); + $pagequery = Base::PAGE_OPENSEARCH_QUERY; + // Special case when no databases were chosen, we search on all databases + if (is_array ($config['calibre_directory']) && $multi === "1") { + $i = 0; + foreach (array_keys ($config['calibre_directory']) as $key) { + Base::clearDb (); + array_push ($out, array ("title" => $key, + "class" => "tt-header", + "navlink" => "index.php?db={$i}")); + list ($array, $total) = Book::getBooksByStartingLetter ('%' . $query, 1, $i, 5); + array_push ($out, array ("title" => str_format (localize("bookword", $total), $total), + "class" => "", + "navlink" => "index.php?page={$pagequery}&query={$query}&db={$i}&scope=book")); + $i++; + } + return $out; + } + + + + $arrayTag = Tag::getAllTagsByQuery ($query, 1, NULL, 5); + + $arraySeries = Serie::getAllSeriesByQuery ($query); + + $arrayAuthor = Author::getAuthorsByStartingLetter ('%' . $query); + + $arrayBook = Book::getBooksByStartingLetter ('%' . $query, 1, NULL, 5); + foreach (array ("book" => $arrayBook, "author" => $arrayAuthor, "series" => $arraySeries, "tag" => $arrayTag) as $key => $array) { $i = 0; - $pagequery = Base::PAGE_OPENSEARCH_QUERY; - if (count($array) > 0) { + if (count ($array) == 2 && is_array ($array [0])) { + $total = $array [1]; + $array = $array [0]; + } else { + $total = count($array); + } + if ($total > 0) { // Comment to help the perl i18n script // str_format (localize("bookword", count($array)) // str_format (localize("authorword", count($array) // str_format (localize("seriesword", count($array) // str_format (localize("tagword", count($array) - array_push ($out, array ("title" => str_format (localize("{$key}word", count($array)), count($array)), + array_push ($out, array ("title" => str_format (localize("{$key}word", $total), $total), "class" => "tt-header", "navlink" => "index.php?page={$pagequery}&query={$query}&db={$database}&scope={$key}")); } @@ -636,6 +663,7 @@ function getJson ($complete = false) { $out ["databaseId"] = GetUrlParam (DB, ""); $out ["databaseName"] = Base::getDbName (); $out ["page"] = $page; + $out ["multipleDatabase"] = Base::isMultipleDatabaseEnabled () ? 1 : 0; $out ["entries"] = $entries; $out ["isPaginated"] = 0; if ($currentPage->isPaginated ()) { diff --git a/lang/Localization_ru.json b/lang/Localization_ru.json index 846ddf7..261c28c 100644 --- a/lang/Localization_ru.json +++ b/lang/Localization_ru.json @@ -1,73 +1,73 @@ { -"##TODO##about.title":"About COPS", -"allbooks.alphabetical.many":"Алфавитный указатель всех {0} книг", -"##TODO##allbooks.alphabetical.none":"Alphabetical index of absolutely no book", +"##TODO##about.title":"О Программе COPS", +"allbooks.alphabetical.many":"Алфавитный указатель {0} книг", +"##TODO##allbooks.alphabetical.none":"Алфавитный указатель книг без названия", "allbooks.alphabetical.one":"Алфавитный указатель одной книги", "allbooks.title":"Все книги", "authors.alphabetical.many":"Алфавитный указатель для {0} авторов", -"##TODO##authors.alphabetical.none":"Alphabetical index of absolutely no author", +"##TODO##authors.alphabetical.none":"Алфавитный указатель книг без автора", "authors.alphabetical.one":"Алфавитный указатель для одного автора", "authors.title":"Авторы", "authorword.many":"{0} авторов(а)", -"authorword.none":"Нет авторов", +"authorword.none":"Нет автора", "authorword.one":"1 автор", "bookentry.author":"{0} из {1}", "bookword.many":"{0} книг(и)", "bookword.none":"Нет книг", "bookword.one":"1 книга", "bookword.title":"Книги", -"##TODO##cog.alternate":"Search, sort and filters", +"##TODO##cog.alternate":"Поиск, сортировка и фильтры", "content.series":"Серии:", "content.series.data":"Книга {0} в {1} серии", "content.summary":"Краткое содержание:", -"##TODO##customize.email":"Set your email (to allow book emailing)", -"##TODO##customize.fancybox":"Use a Lightbox", -"##TODO##customize.filter":"Enable tag filtering", -"##TODO##customize.paging":"Max number of books per page (-1 to disable)", -"##TODO##customize.style":"Theme", -"##TODO##customize.title":"Customize COPS UI", -"##TODO##home.alternate":"Home", -"##TODO##i18n.coversection":"Cover", +"##TODO##customize.email":"Укажите Ваш email (для отправки книг по электронной почте)", +"##TODO##customize.fancybox":"Использовать Lightbox", +"##TODO##customize.filter":"Включить фильтрацию по меткам", +"##TODO##customize.paging":"Макс. число книг на странице (-1 - не ограничивать)", +"##TODO##customize.style":"Тема", +"##TODO##customize.title":"Изменить Интерфейс COPS", +"##TODO##home.alternate":"Домой", +"##TODO##i18n.coversection":"Обложка", "language.title":"Язык", -"##TODO##languages.alphabetical.many":"Alphabetical index of the {0} languages", -"##TODO##languages.alphabetical.none":"Alphabetical index of absolutely no language", -"##TODO##languages.alphabetical.one":"Alphabetical index of the single language", -"##TODO##languages.title":"Languages", -"##TODO##mail.messagenotsent":"Message could not be sent.", -"##TODO##mail.messagesent":"Message has been sent", -"##TODO##paging.next.alternate":"Next", -"##TODO##paging.previous.alternate":"Previous", -"##TODO##permalink.alternate":"Permalink", -"##TODO##pubdate.title":"Publication year", +"##TODO##languages.alphabetical.many":"Алфавитный указатель {0} языков", +"##TODO##languages.alphabetical.none":"Алфавитный указатель {0} книг без языка", +"##TODO##languages.alphabetical.one":"Алфавитный указатель для одного языка", +"##TODO##languages.title":"Языки", +"##TODO##mail.messagenotsent":"Сообщение не может быть отправлено", +"##TODO##mail.messagesent":"Сообщение было отправлено", +"##TODO##paging.next.alternate":"След.", +"##TODO##paging.previous.alternate":"Предыд.", +"##TODO##permalink.alternate":"Постоянная Ссылка", +"##TODO##pubdate.title":"Дата публикации", "recent.list":"{0} недавно поступивших(ие) книг(и)", "recent.title":"Недавние поступления", -"##TODO##search.alternate":"Search", -"##TODO##search.result":"Search result for *{0}*", -"##TODO##search.result.author":"Search result for *{0}* in authors", -"##TODO##search.result.book":"Search result for *{0}* in books", -"##TODO##search.result.series":"Search result for *{0}* in series", -"##TODO##search.result.tag":"Search result for *{0}* in tags", -"##TODO##search.sortorder.asc":"Asc", -"##TODO##search.sortorder.desc":"Desc", +"##TODO##search.alternate":"Поиск", +"##TODO##search.result":"Результаты поиска для *{0}*", +"##TODO##search.result.author":"Результаты поиска для *{0}* авторов", +"##TODO##search.result.book":"Результаты поиска для *{0}* книг", +"##TODO##search.result.series":"Результаты поиска для *{0}* серий", +"##TODO##search.result.tag":"Результаты поиска для *{0}* меток", +"##TODO##search.sortorder.asc":"Возр.", +"##TODO##search.sortorder.desc":"Убыв.", "series.alphabetical.many":"Алфавитный указатель для {0} серий", -"##TODO##series.alphabetical.none":"Alphabetical index of absolutely no series", +"##TODO##series.alphabetical.none":"Алфавитный указатель для книг без серии", "series.alphabetical.one":"Алфавитный указатель для одной серии", "series.title":"Серии", "seriesword.many":"{0} серий(и)", "seriesword.none":"Нет серий", "seriesword.one":"1 серия", -"##TODO##sort.alternate":"Sort", +"##TODO##sort.alternate":"Сортировка", "splitByLetter.book.other":"Другие книги", "splitByLetter.letter":"{0} начать с {1}", -"tags.alphabetical.many":"Алфавитный указатель для {0} тэгов", -"##TODO##tags.alphabetical.none":"Alphabetical index of absolutely no tag", -"tags.alphabetical.one":"Алфавитный указатель для одного тэга", -"tags.title":"Тэги", -"tagword.many":"{0} тэгов(а)", -"tagword.none":"Нет тэгов", -"tagword.one":"1 тэг", -"tagword.title":"Тэги", -"##TODO##languages.abk":"Abkhaz", +"tags.alphabetical.many":"Алфавитный указатель для {0} меток", +"##TODO##tags.alphabetical.none":"Алфавитный указатель книг без меток", +"tags.alphabetical.one":"Алфавитный указатель для одной метки", +"tags.title":"Метки", +"tagword.many":"{0} метки(ок)", +"tagword.none":"Нет меток", +"tagword.one":"1 метка", +"tagword.title":"Метки", +"##TODO##languages.abk":"Абхазский", "##TODO##languages.aaf":"Afar", "##TODO##languages.afr":"Afrikaans", "##TODO##languages.aka":"Akan", @@ -84,7 +84,7 @@ "##TODO##languages.bam":"Bambara", "##TODO##languages.bak":"Bashkir", "##TODO##languages.eus":"Basque", -"##TODO##languages.bel":"Belarusian", +"##TODO##languages.bel":"Белорусский", "##TODO##languages.ben":"Bengali", "##TODO##languages.bih":"Bihari", "##TODO##languages.bis":"Bislama", @@ -94,10 +94,10 @@ "##TODO##languages.mya":"Burmese", "##TODO##languages.cat":"Catalan", "##TODO##languages.cha":"Chamorro", -"##TODO##languages.che":"Chechen", +"##TODO##languages.che":"Чеченский", "##TODO##languages.nya":"Chichewa", "##TODO##languages.zho":"Chinese", -"##TODO##languages.chv":"Chuvash", +"##TODO##languages.chv":"Чувашский", "##TODO##languages.cor":"Cornish", "##TODO##languages.cos":"Corsican", "##TODO##languages.cre":"Cree", @@ -189,7 +189,7 @@ "##TODO##languages.chu":"Old Church Slavonic", "##TODO##languages.orm":"Oromo", "##TODO##languages.ori":"Oriya", -"##TODO##languages.oss":"Ossetian", +"##TODO##languages.oss":"Осетинский", "##TODO##languages.pan":"Panjabi", "##TODO##languages.pli":"Pāli", "##TODO##languages.fas":"Persian", @@ -200,7 +200,7 @@ "##TODO##languages.roh":"Romansh", "##TODO##languages.run":"Kirundi", "##TODO##languages.ron":"Romanian", -"##TODO##languages.rus":"Russian", +"##TODO##languages.rus":"Русский", "##TODO##languages.san":"Sanskrit", "##TODO##languages.srd":"Sardinian", "##TODO##languages.snd":"Sindhi", @@ -232,7 +232,7 @@ "##TODO##languages.ton":"Tonga", "##TODO##languages.tur":"Turkish", "##TODO##languages.tso":"Tsonga", -"##TODO##languages.tat":"Tatar", +"##TODO##languages.tat":"Татарский", "##TODO##languages.twi":"Twi", "##TODO##languages.tah":"Tahitian", "##TODO##languages.uig":"Uighur", diff --git a/tag.php b/tag.php index ad20d7f..38fd2a4 100644 --- a/tag.php +++ b/tag.php @@ -62,14 +62,11 @@ order by tags.name'); return $entryArray; } - public static function getAllTagsByQuery($query) { - $result = parent::getDb ()->prepare('select tags.id as id, tags.name as name, count(*) as count -from tags, books_tags_link -where tags.id = tag and tags.name like ? -group by tags.id, tags.name -order by tags.name'); + public static function getAllTagsByQuery($query, $n, $database = NULL, $numberPerPage = NULL) { + $columns = "tags.id as id, tags.name as name, (select count(*) from books_tags_link where tags.id = tag) as count"; + $sql = 'select {0} from tags where tags.name like ? {1} order by tags.name'; + list ($totalNumber, $result) = parent::executeQuery ($sql, $columns, "", array ('%' . $query . '%'), $n, $database, $numberPerPage); $entryArray = array(); - $result->execute (array ('%' . $query . '%')); while ($post = $result->fetchObject ()) { $tag = new Tag ($post->id, $post->name); @@ -77,6 +74,6 @@ order by tags.name'); str_format (localize("bookword", $post->count), $post->count), "text", array ( new LinkNavigation ($tag->getUri ())))); } - return $entryArray; + return array ($entryArray, $totalNumber); } } diff --git a/test/pageTest.php b/test/pageTest.php index d5af42d..6587605 100644 --- a/test/pageTest.php +++ b/test/pageTest.php @@ -286,9 +286,22 @@ class PageTest extends PHPUnit_Framework_TestCase $n = "1"; $database = NULL; + $config['cops_titles_split_first_letter'] = 0; + $currentPage = Page::getPage ($page, $qid, $query, $n); $currentPage->InitializeContent (); + $this->assertEquals ("All books", $currentPage->title); + $this->assertCount (14, $currentPage->entryArray); + $this->assertEquals ("The Adventures of Sherlock Holmes", $currentPage->entryArray [0]->title); + $this->assertEquals ("Alice's Adventures in Wonderland", $currentPage->entryArray [1]->title); + $this->assertTrue ($currentPage->ContainsBook ()); + + $config['cops_titles_split_first_letter'] = 1; + + $currentPage = Page::getPage ($page, $qid, $query, $n); + $currentPage->InitializeContent (); + $this->assertEquals ("All books", $currentPage->title); $this->assertCount (9, $currentPage->entryArray); $this->assertEquals ("A", $currentPage->entryArray [0]->title); diff --git a/util.js b/util.js index 2e0802b..87c57f7 100644 --- a/util.js +++ b/util.js @@ -258,7 +258,7 @@ updatePage = function (data) { } else { $("#sortForm").hide (); } - + $('input[name=query]').typeahead([ { name: 'search', @@ -268,9 +268,13 @@ updatePage = function (data) { limit: 24, template: templateSuggestion, remote: { - url: 'getJSON.php?search=1&db=%DB&query=%QUERY', + url: 'getJSON.php?search=1&db=%DB&query=%QUERY&multi=%MULTI', replace: function (url, query) { - return url.replace('%QUERY', query).replace('%DB', currentData.databaseId); + var multi = 0; + if (currentData.multipleDatabase === 1 && currentData.databaseId === "") { + multi = 1; + } + return url.replace('%QUERY', query).replace('%DB', currentData.databaseId).replace ("%MULTI", multi); } } }