diff --git a/.htaccess b/.htaccess index d20a79d..80f0908 100644 --- a/.htaccess +++ b/.htaccess @@ -2,7 +2,7 @@ DirectoryIndex index.php - XSendFile on + XSendFile on @@ -61,6 +61,12 @@ ExpiresByType text/javascript "access plus 1 year" ########################################### # Uncomment if you wish to protect access with a password ########################################### +# If your covers and books are not available as soon as you protect it +# You can try replacing the FilesMatch directive by this one +# +# If helps for Sony PRS-TX and Aldiko, beware fetch.php can be accessed +# with authentication +########################################### # #AuthUserFile /path/to/file #AuthGroupFile /dev/null diff --git a/.travis.yml b/.travis.yml index 86c8cda..7d208da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,13 @@ script: - phpunit - jshint --verbose --show-non-errors util.js - php test/coverage-checker.php clover.xml 45 - \ No newline at end of file +after_success: + - chmod +x test/prepareSauceTest.sh + - test/prepareSauceTest.sh +env: + global: + - SAUCE_USERNAME=seblucas + - secure: VVxocvmz6WYr3tZSTA42M/LUhaHoBWw5onh85hnquoMaxspd3tDCyfQIowTTmEXikRh2T0CkTH7X3dhVwRTd/Ha9isja1qDo9Lc2flGCoWICF7WFZuom084+d+O+EWx4WZMAw4Lz4w6a5xflpPKnzNs9B0+de0BdTlQ5qSXVrcA= +addons: + hosts: + - cops-travis diff --git a/CHANGELOG b/CHANGELOG index 1632745..940f8aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,11 +4,14 @@ * Updated the way locales are handled. Should be easier to add new languages. * Fixed display of Cyrillic characters. * Upgraded doT to version 1.0.1, Magnific-Popup to 0.9.8, Normalize.css to 2.1.3, Jquery-cookie to 1.4.0. - * Upgraded * Fixed OPDS stream validity. Reported by Didier. * Added a new check in checkconfig.php to detect case problem between the actual path and the path stored in Calibre database. Try checkconfig.php?full=1. Reported by Ruud. * Fixed the display of the rating stars with Chrome. Thanks to At_Libitum. - * Added a new parameter to avoid splitting the books by first letter. Thanks to At_Libitum. + * Added a new parameter ($config['cops_titles_split_first_letter']) to avoid splitting the books by first letter. Thanks to At_Libitum. + * Fixed non compliant OPDS search (for Stanza, Moon+ Reader, ...). Reported by At_Libitum. + * Fixed the redirection in case the Calibre database is not found. Reported by At_Libitum + * Changed .htaccess to allow the use of password protected catalogs with Sony's eReader (PRS-TX). Thanks to Ruud for the beta testing. + * Updated Chinese, German, Norwegian, Portuguese, Russian translations. Thanks to all the translators. 0.6.2 - 20130913 * Added server side rendering for devices like PRS-TX / Kindle / Cybook. Thanks to all the testers. diff --git a/README.md b/README.md index 8f30b32..b0e5b4d 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,9 @@ cops Calibre OPDS (and HTML) PHP Server : light alternative to Calibre content server / Calibre2OPDS See : http://blog.slucas.fr/en/oss/calibre-opds-php-server + +[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/seblucas/cops/badges/quality-score.png?s=e1c87a92ef90b8d666cd9bd4f3612bd10db84364)](https://scrutinizer-ci.com/g/seblucas/cops/) + +[![Code Coverage](https://scrutinizer-ci.com/g/seblucas/cops/badges/coverage.png?s=1e21d8c3bf96d7b0b7cc0e54429fa897ddea1506)](https://scrutinizer-ci.com/g/seblucas/cops/) + +[![Build Status](https://travis-ci.org/seblucas/cops.png)](https://travis-ci.org/seblucas/cops) diff --git a/base.php b/base.php index e450f3d..c23e100 100644 --- a/base.php +++ b/base.php @@ -16,6 +16,13 @@ function useServerSideRendering () { return preg_match("/" . $config['cops_server_side_render'] . "/", $_SERVER['HTTP_USER_AGENT']); } +function getQueryString () { + if ( isset($_SERVER['QUERY_STRING']) ) { + return $_SERVER['QUERY_STRING']; + } + return ""; +} + function getURLParam ($name, $default = NULL) { if (!empty ($_GET) && isset($_GET[$name]) && $_GET[$name] != "") { return $_GET[$name]; @@ -192,6 +199,9 @@ function localize($phrase, $count=-1, $reset=false) { } function addURLParameter($urlParams, $paramName, $paramValue) { + if (empty ($urlParams)) { + $urlParams = ""; + } $start = ""; if (preg_match ("#^\?(.*)#", $urlParams, $matches)) { $start = "?"; @@ -464,7 +474,7 @@ class Page $tags = Tag::getCount(); if (!is_null ($tags)) array_push ($this->entryArray, $tags); - $languages = Language::getCount(); + $languages = Language::getCount(); if (!is_null ($languages)) array_push ($this->entryArray, $languages); foreach ($config['cops_calibre_custom_column'] as $lookup) { $customId = CustomColumn::getCustomId ($lookup); @@ -487,8 +497,8 @@ class Page public function getNextLink () { - $currentUrl = $_SERVER['QUERY_STRING']; - $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . $_SERVER['QUERY_STRING']); + $currentUrl = getQueryString (); + $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . getQueryString ()); if (($this->n) * getCurrentOption ("max_item_per_page") < $this->totalNumber) { return new LinkNavigation ($currentUrl . "&n=" . ($this->n + 1), "next", localize ("paging.next.alternate")); } @@ -497,8 +507,8 @@ class Page public function getPrevLink () { - $currentUrl = $_SERVER['QUERY_STRING']; - $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . $_SERVER['QUERY_STRING']); + $currentUrl = getQueryString (); + $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . getQueryString ()); if ($this->n > 1) { return new LinkNavigation ($currentUrl . "&n=" . ($this->n - 1), "previous", localize ("paging.previous.alternate")); } @@ -795,7 +805,6 @@ class PageCustomize extends Page { public function InitializeContent () { - global $config; $this->title = localize ("customize.title"); $this->entryArray = array (); @@ -926,15 +935,22 @@ abstract class Base public static function getDbFileName ($database = NULL) { return self::getDbDirectory ($database) .'metadata.db'; } + + private static function error () { + header("location: checkconfig.php?err=1"); + exit(); + } public static function getDb ($database = NULL) { - global $config; if (is_null (self::$db)) { try { - self::$db = new PDO('sqlite:'. self::getDbFileName ($database)); + if (is_readable (self::getDbFileName ($database))) { + self::$db = new PDO('sqlite:'. self::getDbFileName ($database)); + } else { + self::error (); + } } catch (Exception $e) { - header("location: checkconfig.php?err=1"); - exit(); + self::error (); } } return self::$db; @@ -945,7 +961,6 @@ abstract class Base } public static function executeQuery($query, $columns, $filter, $params, $n, $database = NULL, $numberPerPage = NULL) { - global $config; $totalResult = -1; if (is_null ($numberPerPage)) { diff --git a/book.php b/book.php index 7eb1ab6..e539920 100644 --- a/book.php +++ b/book.php @@ -452,7 +452,7 @@ class Book extends Base { { if ($data->isKnownType ()) { - array_push ($linkArray, $data->getDataLink (Link::OPDS_ACQUISITION_TYPE, "Download")); + array_push ($linkArray, $data->getDataLink (Link::OPDS_ACQUISITION_TYPE, $data->format)); } } diff --git a/checkconfig.php b/checkconfig.php index 57f97a7..aac84bc 100644 --- a/checkconfig.php +++ b/checkconfig.php @@ -129,6 +129,7 @@ Please check } ?> +

Check if Calibre database file can be opened with PHP

@@ -182,6 +183,7 @@ Please check

+
diff --git a/index.php b/index.php index d2a7a04..b123250 100644 --- a/index.php +++ b/index.php @@ -24,12 +24,27 @@ exit (); } - header ("Content-Type:text/html;charset=utf-8"); $page = getURLParam ("page", Base::PAGE_INDEX); $query = getURLParam ("query"); $qid = getURLParam ("id"); $n = getURLParam ("n", "1"); $database = GetUrlParam (DB); + + + // Access the database ASAP to be sure it's readable, redirect if that's not the case. + // It has to be done before any header is sent. + if (is_array ($config['calibre_directory']) && is_null ($database)) { + $i = 0; + foreach (array_keys ($config['calibre_directory']) as $key) { + $test = Base::getDb ($i); + Base::clearDb (); + $i++; + } + } else { + $test = Base::getDb (); + } + + header ("Content-Type:text/html;charset=utf-8"); ?> @@ -66,7 +81,7 @@ $(document).ready(function() { // Handler for .ready() called. - var url = ""; + var url = ""; $.when($.get('templates/default/header.html'), $.get('templates/default/footer.html'), @@ -98,7 +113,9 @@ updatePage (data [0]); cache.put (url, data [0]); - history.replaceState(url, "", window.location); + if (isPushStateEnabled) { + history.replaceState(url, "", window.location); + } handleLinks (); }); diff --git a/test/OPDSTest.php b/test/OPDSTest.php index edaa245..9d6aafc 100644 --- a/test/OPDSTest.php +++ b/test/OPDSTest.php @@ -38,10 +38,8 @@ class OpdsTest extends PHPUnit_Framework_TestCase global $config; $page = Base::PAGE_INDEX; $query = NULL; - $search = NULL; $qid = NULL; $n = "1"; - $database = NULL; $_SERVER['QUERY_STRING'] = ""; $config['cops_subtitle_default'] = "My subtitle"; @@ -66,10 +64,8 @@ class OpdsTest extends PHPUnit_Framework_TestCase "One book" => dirname(__FILE__) . "/BaseWithOneBook/"); $page = Base::PAGE_AUTHOR_DETAIL; $query = NULL; - $search = NULL; $qid = "1"; $n = "1"; - $database = NULL; $_SERVER['QUERY_STRING'] = "page=" . Base::PAGE_AUTHOR_DETAIL . "&id=1"; $_GET ["db"] = "0"; @@ -87,10 +83,8 @@ class OpdsTest extends PHPUnit_Framework_TestCase global $config; $page = Base::PAGE_AUTHOR_DETAIL; $query = NULL; - $search = NULL; $qid = "1"; $n = "1"; - $database = NULL; $_SERVER['QUERY_STRING'] = "page=" . Base::PAGE_AUTHOR_DETAIL . "&id=1&n=1"; $config['cops_max_item_per_page'] = 2; diff --git a/test/Sauce.php b/test/Sauce.php new file mode 100644 index 0000000..db778eb --- /dev/null +++ b/test/Sauce.php @@ -0,0 +1,140 @@ + 'firefox', + 'desiredCapabilities' => array( + 'version' => '15', + 'platform' => 'Windows 2012', + ) + ), + // run IE9 on Windows 7 on Sauce + array( + 'browserName' => 'internet explorer', + 'desiredCapabilities' => array( + 'version' => '9', + 'platform' => 'Windows 7', + ) + ), + // run IE10 on Windows 8 on Sauce + array( + 'browserName' => 'internet explorer', + 'desiredCapabilities' => array( + 'version' => '10', + 'platform' => 'Windows 8', + ) + ), + // run Opera 12 on Windows 7 on Sauce + array( + 'browserName' => 'opera', + 'desiredCapabilities' => array( + 'version' => '12', + 'platform' => 'Windows 7', + ) + ), + // run Mobile Safari on iOS + array( + 'browserName' => '', + 'desiredCapabilities' => array( + 'app' => 'safari', + 'device' => 'iPhone Simulator', + 'version' => '6.1', + 'platform' => 'Mac 10.8', + ) + ), + // run Chrome on Linux on Sauce + array( + 'browserName' => 'chrome', + 'desiredCapabilities' => array( + 'version' => '30', + 'platform' => 'Linux' + ) + ) + // run Mobile Browser on Android + // array( + // 'browserName' => 'Android', + // 'desiredCapabilities' => array( + // 'version' => '4.0', + // 'platform' => 'Linux', + // ) + // ) + + // run Chrome locally + //array( + //'browserName' => 'chrome', + //'local' => true, + //'sessionStrategy' => 'shared' + //) + ); + + public function setUp() + { + if (isset ($_SERVER["TRAVIS_JOB_NUMBER"])) { + $caps = $this->getDesiredCapabilities(); + $caps['build'] = getenv ("TRAVIS_JOB_NUMBER"); + $this->setDesiredCapabilities($caps); + } + parent::setUp (); + } + + public function setUpPage() + { + if (isset ($_SERVER["TRAVIS_JOB_NUMBER"])) { + $this->url('http://127.0.0.1:8888/index.php'); + } else { + $this->url('http://cops-demo.slucas.fr/index.php'); + } + + $driver = $this; + $title_test = function($value) use ($driver) { + $text = $driver->byXPath('//h1')->text (); + return $text == $value; + }; + + $this->spinAssert("Home Title", $title_test, [ "COPS DEMO" ]); + } + + public function string_to_ascii($string) + { + $ascii = NULL; + + for ($i = 0; $i < strlen($string); $i++) + { + $ascii += ord($string[$i]); + } + + return mb_detect_encoding($string) . "X" . $ascii; + } + + public function testTitle() + { + $driver = $this; + $title_test = function($value) use ($driver) { + $text = $driver->byXPath('//h1')->text (); + return $text == $value; + }; + + $author = $this->byXPath ('//h2[contains(text(), "Authors")]'); + $author->click (); + + $this->spinAssert("Author Title", $title_test, [ "AUTHORS" ]); + } + + public function testCog() + { + $cog = $this->byId ("searchImage"); + + $search = $this->byName ("query"); + $this->assertFalse ($search->displayed ()); + + $cog->click (); + + $search = $this->byName ("query"); + $this->assertTrue ($search->displayed ()); + } +} diff --git a/test/baseTest.php b/test/baseTest.php index 7b2c21c..423930d 100644 --- a/test/baseTest.php +++ b/test/baseTest.php @@ -42,4 +42,19 @@ class BaseTest extends PHPUnit_Framework_TestCase $_SERVER['HTTP_ACCEPT_LANGUAGE'] = "en"; localize ("authors.title", -1, true); } + + public function testBaseFunction () { + global $config; + + $this->assertFalse (Base::isMultipleDatabaseEnabled ()); + $this->assertEquals (array ("" => dirname(__FILE__) . "/BaseWithSomeBooks/"), Base::getDbList ()); + + $config['calibre_directory'] = array ("Some books" => dirname(__FILE__) . "/BaseWithSomeBooks/", + "One book" => dirname(__FILE__) . "/BaseWithOneBook/"); + + $this->assertTrue (Base::isMultipleDatabaseEnabled ()); + $this->assertEquals ("Some books", Base::getDbName (0)); + $this->assertEquals ("One book", Base::getDbName (1)); + $this->assertEquals ($config['calibre_directory'], Base::getDbList ()); + } } \ No newline at end of file diff --git a/test/bookTest.php b/test/bookTest.php index 4a696d9..ed5537c 100644 --- a/test/bookTest.php +++ b/test/bookTest.php @@ -118,6 +118,7 @@ class BookTest extends PHPUnit_Framework_TestCase { // All books by first letter list ($entryArray, $totalNumber) = Book::getBooksByStartingLetter ("T", -1); + $this->assertEquals (-1, $totalNumber); $this->assertCount (3, $entryArray); } @@ -200,4 +201,30 @@ class BookTest extends PHPUnit_Framework_TestCase $_GET["search"] = NULL; } + public function testTypeaheadSearchMultiDatabase () + { + global $config; + $_GET["query"] = "art"; + $_GET["search"] = "1"; + $_GET["multi"] = "1"; + + $config['calibre_directory'] = array ("Some books" => dirname(__FILE__) . "/BaseWithSomeBooks/", + "One book" => dirname(__FILE__) . "/BaseWithOneBook/"); + + $array = getJson (); + + $this->assertCount (4, $array); + $this->assertEquals ("Some books", $array[0]["title"]); + $this->assertEquals ("No book", $array[1]["title"]); + $this->assertEquals ("One book", $array[2]["title"]); + $this->assertEquals ("1 book", $array[3]["title"]); + + $_GET["query"] = NULL; + $_GET["search"] = NULL; + } + + public function tearDown () { + Base::clearDb (); + } + } \ No newline at end of file diff --git a/test/config_local.php.sauce b/test/config_local.php.sauce new file mode 100644 index 0000000..7e3c310 --- /dev/null +++ b/test/config_local.php.sauce @@ -0,0 +1,17 @@ + dirname(__FILE__) . "/BaseWithOneBook/"); $page = Base::PAGE_INDEX; $query = NULL; - $search = NULL; $qid = NULL; $n = "1"; - $database = NULL; $currentPage = Page::getPage ($page, $qid, $query, $n); $currentPage->InitializeContent (); @@ -34,4 +32,31 @@ class PageMultiDatabaseTest extends PHPUnit_Framework_TestCase $this->assertEquals ("1 book", $currentPage->entryArray [1]->content); $this->assertFalse ($currentPage->ContainsBook ()); } -} \ No newline at end of file + + public function testPageSearchXXX () + { + global $config; + $config['calibre_directory'] = array ("Some books" => dirname(__FILE__) . "/BaseWithSomeBooks/", + "One book" => dirname(__FILE__) . "/BaseWithOneBook/"); + $page = Base::PAGE_OPENSEARCH_QUERY; + $query = "art"; + $qid = NULL; + $n = "1"; + + $currentPage = Page::getPage ($page, $qid, $query, $n); + $currentPage->InitializeContent (); + + $this->assertEquals ("Search result for *art*", $currentPage->title); + $this->assertCount (2, $currentPage->entryArray); + $this->assertEquals ("Some books", $currentPage->entryArray [0]->title); + $this->assertEquals ("10 books", $currentPage->entryArray [0]->content); + $this->assertEquals ("One book", $currentPage->entryArray [1]->title); + $this->assertEquals ("1 book", $currentPage->entryArray [1]->content); + $this->assertFalse ($currentPage->ContainsBook ()); + } + + public static function tearDownAfterClass () { + Base::clearDb (); + } + +} diff --git a/test/pageTest.php b/test/pageTest.php index e5657c5..61bbbbf 100644 --- a/test/pageTest.php +++ b/test/pageTest.php @@ -328,8 +328,7 @@ class PageTest extends PHPUnit_Framework_TestCase $page = Base::PAGE_SERIE_DETAIL; $query = NULL; $qid = "1"; - $n = "1"; - + $n = "1"; $currentPage = Page::getPage ($page, $qid, $query, $n); $currentPage->InitializeContent (); @@ -374,7 +373,6 @@ class PageTest extends PHPUnit_Framework_TestCase public function testPageAllTags () { - global $config; $page = Base::PAGE_ALL_TAGS; $query = NULL; $qid = NULL; @@ -522,7 +520,6 @@ class PageTest extends PHPUnit_Framework_TestCase public function testPageSearchScopeAuthors () { - global $config; $page = Base::PAGE_OPENSEARCH_QUERY; $qid = NULL; $n = "1"; @@ -543,7 +540,6 @@ class PageTest extends PHPUnit_Framework_TestCase public function testPageSearchScopeSeries () { - global $config; $page = Base::PAGE_OPENSEARCH_QUERY; $qid = NULL; $n = "1"; @@ -564,7 +560,6 @@ class PageTest extends PHPUnit_Framework_TestCase public function testPageSearchScopeBooks () { - global $config; $page = Base::PAGE_OPENSEARCH_QUERY; $qid = NULL; $n = "1"; @@ -584,7 +579,6 @@ class PageTest extends PHPUnit_Framework_TestCase public function testPageSearchScopePublishers () { - global $config; $page = Base::PAGE_OPENSEARCH_QUERY; $qid = NULL; $n = "1"; diff --git a/test/prepareSauceTest.sh b/test/prepareSauceTest.sh new file mode 100644 index 0000000..5355c6e --- /dev/null +++ b/test/prepareSauceTest.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +if [[ -z $SAUCE_ACCESS_KEY ]] + then + echo "No Sauce Api Key (Pull request)" + exit +fi + +PHP_VERSION=`php -v|grep --only-matching --perl-regexp "PHP 5\.\\d+"` +echo $PHP_VERSION + + +if [[ $PHP_VERSION != "PHP 5.4" ]] + then + echo "Bad PHP version" + exit +fi + +echo "Good PHP version" + +curl https://gist.github.com/seblucas/7692094/raw/e2a090e6ea639a0d700e6d02cee048fa2f6c8617/sauce_connect_setup.sh | bash +curl -s https://raw.github.com/jlipps/sausage-bun/master/givememysausage.php | php +cp -v test/config_local.php.sauce config_local.php +php -S 127.0.0.1:8888 & +vendor/bin/phpunit --no-configuration test/Sauce.php + + diff --git a/util.js b/util.js index 87c57f7..3c5b88d 100644 --- a/util.js +++ b/util.js @@ -281,7 +281,11 @@ updatePage = function (data) { ]); $('input[name=query]').bind('typeahead:selected', function(obj, datum) { - navigateTo (datum.navlink); + if (isPushStateEnabled) { + navigateTo (datum.navlink); + } else { + window.location = datum.navlink; + } }); };