Calibre OPDS (and HTML) PHP Server : web-based light alternative to Calibre content server / Calibre2OPDS to serve ebooks (epub, mobi, pdf, ...) http://blog.slucas.fr/en/oss/calibre-opds-php-server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

12 years ago
12 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
10 years ago
11 years ago
9 years ago
9 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
**2012-11-22** **Added global support for publishers** Files modified: *base.php* - changed class Entry, - adding a constant ```cops:publishers``` to the icon array for the feed. - changed class Page - added branches to the page selector switch - changed Page->public function InitializeContent - added call to pull publisher count from database - changed class PageAllBooks - changed it so ```getCurrentOption``` is actually used... - added page descendant class ```PageAllPublishers``` - handles pulling the publishers category from database - added page descendant class ```PagePublisherDetail``` - handles pulling the books per publisher data from database - changed class PageQueryResult - added constant and switches for publisher search scope - abstract class Base - added constants for the publisher pages *book.php* - added require statement for publisher.php - added ```SQL_BOOKS_BY_PUBLISHER``` query to retrieve books by publisher. - changed class Book - added query constant - added publisher item - added test in case no known publisher - added publishername and url array elements for the JSON output - added public function ```getPublisher``` - added public static function ```getBooksByPublisher``` to fire the query - changed function getJson - added publisher category to search - added publishername (single) and publishertitle(plural) localization entries to i18n translation array *index.php* - added require statement for publisher.php *lang/Localization_en.json - added new localization entries for publisher labels (see below) ``` "publisher.alphabetical.many":"Alphabetical index of the {0} publishers", "publisher.alphabetical.none":"Alphabetical index of absolutely no publisher", "publisher.alphabetical.one":"Alphabetical index of the single publisher", "publisher.name":"Publisher", "publisher.title":"Publishers", "publisherword.many":"{0} publishers", "publisherword.none":"No publisher", "publisherword.one":"1 publisher", "search.result.publisher":"Search result for *{0}* in publishers", ``` *templates\bookdetail.html* - added publisher label and item to bookdetail popup *test\bookTest.php* - added indices and names of publishers added to testdatabase as comment - added test function ```testGetBooksByPublisher``` - changed test function testGetBookById to add assertion for publisher name - changed test function testTypeaheadSearch to add search on partial publisher name. *test\pageTest.php* - changed test function testPageIndex to insert publisher category and adjust page indices - changed test function testPageIndexWithCustomColum to adjust for the changed page indices - added test function testPageAllPublishers - added test function testPagePublishersDetail - added test function testPageSearchScopePublishers *test\BaseWithSomeBooks\metadata.db* - added 5 publishers spread across all 14 books, replacing the original publisher Feedbooks Files added: *publisher.php*
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
**2012-11-22** **Added global support for publishers** Files modified: *base.php* - changed class Entry, - adding a constant ```cops:publishers``` to the icon array for the feed. - changed class Page - added branches to the page selector switch - changed Page->public function InitializeContent - added call to pull publisher count from database - changed class PageAllBooks - changed it so ```getCurrentOption``` is actually used... - added page descendant class ```PageAllPublishers``` - handles pulling the publishers category from database - added page descendant class ```PagePublisherDetail``` - handles pulling the books per publisher data from database - changed class PageQueryResult - added constant and switches for publisher search scope - abstract class Base - added constants for the publisher pages *book.php* - added require statement for publisher.php - added ```SQL_BOOKS_BY_PUBLISHER``` query to retrieve books by publisher. - changed class Book - added query constant - added publisher item - added test in case no known publisher - added publishername and url array elements for the JSON output - added public function ```getPublisher``` - added public static function ```getBooksByPublisher``` to fire the query - changed function getJson - added publisher category to search - added publishername (single) and publishertitle(plural) localization entries to i18n translation array *index.php* - added require statement for publisher.php *lang/Localization_en.json - added new localization entries for publisher labels (see below) ``` "publisher.alphabetical.many":"Alphabetical index of the {0} publishers", "publisher.alphabetical.none":"Alphabetical index of absolutely no publisher", "publisher.alphabetical.one":"Alphabetical index of the single publisher", "publisher.name":"Publisher", "publisher.title":"Publishers", "publisherword.many":"{0} publishers", "publisherword.none":"No publisher", "publisherword.one":"1 publisher", "search.result.publisher":"Search result for *{0}* in publishers", ``` *templates\bookdetail.html* - added publisher label and item to bookdetail popup *test\bookTest.php* - added indices and names of publishers added to testdatabase as comment - added test function ```testGetBooksByPublisher``` - changed test function testGetBookById to add assertion for publisher name - changed test function testTypeaheadSearch to add search on partial publisher name. *test\pageTest.php* - changed test function testPageIndex to insert publisher category and adjust page indices - changed test function testPageIndexWithCustomColum to adjust for the changed page indices - added test function testPageAllPublishers - added test function testPagePublishersDetail - added test function testPageSearchScopePublishers *test\BaseWithSomeBooks\metadata.db* - added 5 publishers spread across all 14 books, replacing the original publisher Feedbooks Files added: *publisher.php*
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
11 years ago
11 years ago
12 years ago
12 years ago
**2012-11-22** **Added global support for publishers** Files modified: *base.php* - changed class Entry, - adding a constant ```cops:publishers``` to the icon array for the feed. - changed class Page - added branches to the page selector switch - changed Page->public function InitializeContent - added call to pull publisher count from database - changed class PageAllBooks - changed it so ```getCurrentOption``` is actually used... - added page descendant class ```PageAllPublishers``` - handles pulling the publishers category from database - added page descendant class ```PagePublisherDetail``` - handles pulling the books per publisher data from database - changed class PageQueryResult - added constant and switches for publisher search scope - abstract class Base - added constants for the publisher pages *book.php* - added require statement for publisher.php - added ```SQL_BOOKS_BY_PUBLISHER``` query to retrieve books by publisher. - changed class Book - added query constant - added publisher item - added test in case no known publisher - added publishername and url array elements for the JSON output - added public function ```getPublisher``` - added public static function ```getBooksByPublisher``` to fire the query - changed function getJson - added publisher category to search - added publishername (single) and publishertitle(plural) localization entries to i18n translation array *index.php* - added require statement for publisher.php *lang/Localization_en.json - added new localization entries for publisher labels (see below) ``` "publisher.alphabetical.many":"Alphabetical index of the {0} publishers", "publisher.alphabetical.none":"Alphabetical index of absolutely no publisher", "publisher.alphabetical.one":"Alphabetical index of the single publisher", "publisher.name":"Publisher", "publisher.title":"Publishers", "publisherword.many":"{0} publishers", "publisherword.none":"No publisher", "publisherword.one":"1 publisher", "search.result.publisher":"Search result for *{0}* in publishers", ``` *templates\bookdetail.html* - added publisher label and item to bookdetail popup *test\bookTest.php* - added indices and names of publishers added to testdatabase as comment - added test function ```testGetBooksByPublisher``` - changed test function testGetBookById to add assertion for publisher name - changed test function testTypeaheadSearch to add search on partial publisher name. *test\pageTest.php* - changed test function testPageIndex to insert publisher category and adjust page indices - changed test function testPageIndexWithCustomColum to adjust for the changed page indices - added test function testPageAllPublishers - added test function testPagePublishersDetail - added test function testPageSearchScopePublishers *test\BaseWithSomeBooks\metadata.db* - added 5 publishers spread across all 14 books, replacing the original publisher Feedbooks Files added: *publisher.php*
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
**2012-11-22** **Added global support for publishers** Files modified: *base.php* - changed class Entry, - adding a constant ```cops:publishers``` to the icon array for the feed. - changed class Page - added branches to the page selector switch - changed Page->public function InitializeContent - added call to pull publisher count from database - changed class PageAllBooks - changed it so ```getCurrentOption``` is actually used... - added page descendant class ```PageAllPublishers``` - handles pulling the publishers category from database - added page descendant class ```PagePublisherDetail``` - handles pulling the books per publisher data from database - changed class PageQueryResult - added constant and switches for publisher search scope - abstract class Base - added constants for the publisher pages *book.php* - added require statement for publisher.php - added ```SQL_BOOKS_BY_PUBLISHER``` query to retrieve books by publisher. - changed class Book - added query constant - added publisher item - added test in case no known publisher - added publishername and url array elements for the JSON output - added public function ```getPublisher``` - added public static function ```getBooksByPublisher``` to fire the query - changed function getJson - added publisher category to search - added publishername (single) and publishertitle(plural) localization entries to i18n translation array *index.php* - added require statement for publisher.php *lang/Localization_en.json - added new localization entries for publisher labels (see below) ``` "publisher.alphabetical.many":"Alphabetical index of the {0} publishers", "publisher.alphabetical.none":"Alphabetical index of absolutely no publisher", "publisher.alphabetical.one":"Alphabetical index of the single publisher", "publisher.name":"Publisher", "publisher.title":"Publishers", "publisherword.many":"{0} publishers", "publisherword.none":"No publisher", "publisherword.one":"1 publisher", "search.result.publisher":"Search result for *{0}* in publishers", ``` *templates\bookdetail.html* - added publisher label and item to bookdetail popup *test\bookTest.php* - added indices and names of publishers added to testdatabase as comment - added test function ```testGetBooksByPublisher``` - changed test function testGetBookById to add assertion for publisher name - changed test function testTypeaheadSearch to add search on partial publisher name. *test\pageTest.php* - changed test function testPageIndex to insert publisher category and adjust page indices - changed test function testPageIndexWithCustomColum to adjust for the changed page indices - added test function testPageAllPublishers - added test function testPagePublishersDetail - added test function testPageSearchScopePublishers *test\BaseWithSomeBooks\metadata.db* - added 5 publishers spread across all 14 books, replacing the original publisher Feedbooks Files added: *publisher.php*
10 years ago
**2012-11-22** **Added global support for publishers** Files modified: *base.php* - changed class Entry, - adding a constant ```cops:publishers``` to the icon array for the feed. - changed class Page - added branches to the page selector switch - changed Page->public function InitializeContent - added call to pull publisher count from database - changed class PageAllBooks - changed it so ```getCurrentOption``` is actually used... - added page descendant class ```PageAllPublishers``` - handles pulling the publishers category from database - added page descendant class ```PagePublisherDetail``` - handles pulling the books per publisher data from database - changed class PageQueryResult - added constant and switches for publisher search scope - abstract class Base - added constants for the publisher pages *book.php* - added require statement for publisher.php - added ```SQL_BOOKS_BY_PUBLISHER``` query to retrieve books by publisher. - changed class Book - added query constant - added publisher item - added test in case no known publisher - added publishername and url array elements for the JSON output - added public function ```getPublisher``` - added public static function ```getBooksByPublisher``` to fire the query - changed function getJson - added publisher category to search - added publishername (single) and publishertitle(plural) localization entries to i18n translation array *index.php* - added require statement for publisher.php *lang/Localization_en.json - added new localization entries for publisher labels (see below) ``` "publisher.alphabetical.many":"Alphabetical index of the {0} publishers", "publisher.alphabetical.none":"Alphabetical index of absolutely no publisher", "publisher.alphabetical.one":"Alphabetical index of the single publisher", "publisher.name":"Publisher", "publisher.title":"Publishers", "publisherword.many":"{0} publishers", "publisherword.none":"No publisher", "publisherword.one":"1 publisher", "search.result.publisher":"Search result for *{0}* in publishers", ``` *templates\bookdetail.html* - added publisher label and item to bookdetail popup *test\bookTest.php* - added indices and names of publishers added to testdatabase as comment - added test function ```testGetBooksByPublisher``` - changed test function testGetBookById to add assertion for publisher name - changed test function testTypeaheadSearch to add search on partial publisher name. *test\pageTest.php* - changed test function testPageIndex to insert publisher category and adjust page indices - changed test function testPageIndexWithCustomColum to adjust for the changed page indices - added test function testPageAllPublishers - added test function testPagePublishersDetail - added test function testPageSearchScopePublishers *test\BaseWithSomeBooks\metadata.db* - added 5 publishers spread across all 14 books, replacing the original publisher Feedbooks Files added: *publisher.php*
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
**2012-11-22** **Added global support for publishers** Files modified: *base.php* - changed class Entry, - adding a constant ```cops:publishers``` to the icon array for the feed. - changed class Page - added branches to the page selector switch - changed Page->public function InitializeContent - added call to pull publisher count from database - changed class PageAllBooks - changed it so ```getCurrentOption``` is actually used... - added page descendant class ```PageAllPublishers``` - handles pulling the publishers category from database - added page descendant class ```PagePublisherDetail``` - handles pulling the books per publisher data from database - changed class PageQueryResult - added constant and switches for publisher search scope - abstract class Base - added constants for the publisher pages *book.php* - added require statement for publisher.php - added ```SQL_BOOKS_BY_PUBLISHER``` query to retrieve books by publisher. - changed class Book - added query constant - added publisher item - added test in case no known publisher - added publishername and url array elements for the JSON output - added public function ```getPublisher``` - added public static function ```getBooksByPublisher``` to fire the query - changed function getJson - added publisher category to search - added publishername (single) and publishertitle(plural) localization entries to i18n translation array *index.php* - added require statement for publisher.php *lang/Localization_en.json - added new localization entries for publisher labels (see below) ``` "publisher.alphabetical.many":"Alphabetical index of the {0} publishers", "publisher.alphabetical.none":"Alphabetical index of absolutely no publisher", "publisher.alphabetical.one":"Alphabetical index of the single publisher", "publisher.name":"Publisher", "publisher.title":"Publishers", "publisherword.many":"{0} publishers", "publisherword.none":"No publisher", "publisherword.one":"1 publisher", "search.result.publisher":"Search result for *{0}* in publishers", ``` *templates\bookdetail.html* - added publisher label and item to bookdetail popup *test\bookTest.php* - added indices and names of publishers added to testdatabase as comment - added test function ```testGetBooksByPublisher``` - changed test function testGetBookById to add assertion for publisher name - changed test function testTypeaheadSearch to add search on partial publisher name. *test\pageTest.php* - changed test function testPageIndex to insert publisher category and adjust page indices - changed test function testPageIndexWithCustomColum to adjust for the changed page indices - added test function testPageAllPublishers - added test function testPagePublishersDetail - added test function testPageSearchScopePublishers *test\BaseWithSomeBooks\metadata.db* - added 5 publishers spread across all 14 books, replacing the original publisher Feedbooks Files added: *publisher.php*
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
**2012-11-22** **Added global support for publishers** Files modified: *base.php* - changed class Entry, - adding a constant ```cops:publishers``` to the icon array for the feed. - changed class Page - added branches to the page selector switch - changed Page->public function InitializeContent - added call to pull publisher count from database - changed class PageAllBooks - changed it so ```getCurrentOption``` is actually used... - added page descendant class ```PageAllPublishers``` - handles pulling the publishers category from database - added page descendant class ```PagePublisherDetail``` - handles pulling the books per publisher data from database - changed class PageQueryResult - added constant and switches for publisher search scope - abstract class Base - added constants for the publisher pages *book.php* - added require statement for publisher.php - added ```SQL_BOOKS_BY_PUBLISHER``` query to retrieve books by publisher. - changed class Book - added query constant - added publisher item - added test in case no known publisher - added publishername and url array elements for the JSON output - added public function ```getPublisher``` - added public static function ```getBooksByPublisher``` to fire the query - changed function getJson - added publisher category to search - added publishername (single) and publishertitle(plural) localization entries to i18n translation array *index.php* - added require statement for publisher.php *lang/Localization_en.json - added new localization entries for publisher labels (see below) ``` "publisher.alphabetical.many":"Alphabetical index of the {0} publishers", "publisher.alphabetical.none":"Alphabetical index of absolutely no publisher", "publisher.alphabetical.one":"Alphabetical index of the single publisher", "publisher.name":"Publisher", "publisher.title":"Publishers", "publisherword.many":"{0} publishers", "publisherword.none":"No publisher", "publisherword.one":"1 publisher", "search.result.publisher":"Search result for *{0}* in publishers", ``` *templates\bookdetail.html* - added publisher label and item to bookdetail popup *test\bookTest.php* - added indices and names of publishers added to testdatabase as comment - added test function ```testGetBooksByPublisher``` - changed test function testGetBookById to add assertion for publisher name - changed test function testTypeaheadSearch to add search on partial publisher name. *test\pageTest.php* - changed test function testPageIndex to insert publisher category and adjust page indices - changed test function testPageIndexWithCustomColum to adjust for the changed page indices - added test function testPageAllPublishers - added test function testPagePublishersDetail - added test function testPageSearchScopePublishers *test\BaseWithSomeBooks\metadata.db* - added 5 publishers spread across all 14 books, replacing the original publisher Feedbooks Files added: *publisher.php*
10 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341
  1. <?php
  2. /**
  3. * COPS (Calibre OPDS PHP Server) class file
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author S�bastien Lucas <sebastien@slucas.fr>
  7. */
  8. define ("VERSION", "1.0.0RC4");
  9. define ("DB", "db");
  10. date_default_timezone_set($config['default_timezone']);
  11. function useServerSideRendering () {
  12. global $config;
  13. return preg_match("/" . $config['cops_server_side_render'] . "/", $_SERVER['HTTP_USER_AGENT']);
  14. }
  15. function serverSideRender ($data) {
  16. // Get the templates
  17. $theme = getCurrentTemplate ();
  18. $header = file_get_contents('templates/' . $theme . '/header.html');
  19. $footer = file_get_contents('templates/' . $theme . '/footer.html');
  20. $main = file_get_contents('templates/' . $theme . '/main.html');
  21. $bookdetail = file_get_contents('templates/' . $theme . '/bookdetail.html');
  22. $page = file_get_contents('templates/' . $theme . '/page.html');
  23. // Generate the function for the template
  24. $template = new doT ();
  25. $dot = $template->template ($page, array ("bookdetail" => $bookdetail,
  26. "header" => $header,
  27. "footer" => $footer,
  28. "main" => $main));
  29. // If there is a syntax error in the function created
  30. // $dot will be equal to FALSE
  31. if (!$dot) {
  32. return FALSE;
  33. }
  34. // Execute the template
  35. if (!empty ($data)) {
  36. return $dot ($data);
  37. }
  38. return NULL;
  39. }
  40. function getQueryString () {
  41. if ( isset($_SERVER['QUERY_STRING']) ) {
  42. return $_SERVER['QUERY_STRING'];
  43. }
  44. return "";
  45. }
  46. function notFound () {
  47. header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
  48. header("Status: 404 Not Found");
  49. $_SERVER['REDIRECT_STATUS'] = 404;
  50. }
  51. function getURLParam ($name, $default = NULL) {
  52. if (!empty ($_GET) && isset($_GET[$name]) && $_GET[$name] != "") {
  53. return $_GET[$name];
  54. }
  55. return $default;
  56. }
  57. function getCurrentOption ($option) {
  58. global $config;
  59. if (isset($_COOKIE[$option])) {
  60. if (isset($config ["cops_" . $option]) && is_array ($config ["cops_" . $option])) {
  61. return explode (",", $_COOKIE[$option]);
  62. } else {
  63. return $_COOKIE[$option];
  64. }
  65. }
  66. if ($option == "style") {
  67. return "default";
  68. }
  69. if (isset($config ["cops_" . $option])) {
  70. return $config ["cops_" . $option];
  71. }
  72. return "";
  73. }
  74. function getCurrentCss () {
  75. return "templates/" . getCurrentTemplate () . "/styles/style-" . getCurrentOption ("style") . ".css";
  76. }
  77. function getCurrentTemplate () {
  78. return getCurrentOption ("template");
  79. }
  80. function getUrlWithVersion ($url) {
  81. return $url . "?v=" . VERSION;
  82. }
  83. function xml2xhtml($xml) {
  84. return preg_replace_callback('#<(\w+)([^>]*)\s*/>#s', create_function('$m', '
  85. $xhtml_tags = array("br", "hr", "input", "frame", "img", "area", "link", "col", "base", "basefont", "param");
  86. return in_array($m[1], $xhtml_tags) ? "<$m[1]$m[2] />" : "<$m[1]$m[2]></$m[1]>";
  87. '), $xml);
  88. }
  89. function display_xml_error($error)
  90. {
  91. $return = "";
  92. $return .= str_repeat('-', $error->column) . "^\n";
  93. switch ($error->level) {
  94. case LIBXML_ERR_WARNING:
  95. $return .= "Warning $error->code: ";
  96. break;
  97. case LIBXML_ERR_ERROR:
  98. $return .= "Error $error->code: ";
  99. break;
  100. case LIBXML_ERR_FATAL:
  101. $return .= "Fatal Error $error->code: ";
  102. break;
  103. }
  104. $return .= trim($error->message) .
  105. "\n Line: $error->line" .
  106. "\n Column: $error->column";
  107. if ($error->file) {
  108. $return .= "\n File: $error->file";
  109. }
  110. return "$return\n\n--------------------------------------------\n\n";
  111. }
  112. function are_libxml_errors_ok ()
  113. {
  114. $errors = libxml_get_errors();
  115. foreach ($errors as $error) {
  116. if ($error->code == 801) return false;
  117. }
  118. return true;
  119. }
  120. function html2xhtml ($html) {
  121. $doc = new DOMDocument();
  122. libxml_use_internal_errors(true);
  123. $doc->loadHTML('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' .
  124. $html . '</body></html>'); // Load the HTML
  125. $output = $doc->saveXML($doc->documentElement); // Transform to an Ansi xml stream
  126. $output = xml2xhtml($output);
  127. if (preg_match ('#<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></meta></head><body>(.*)</body></html>#ms', $output, $matches)) {
  128. $output = $matches [1]; // Remove <html><body>
  129. }
  130. /*
  131. // In case of error with summary, use it to debug
  132. $errors = libxml_get_errors();
  133. foreach ($errors as $error) {
  134. $output .= display_xml_error($error);
  135. }
  136. */
  137. if (!are_libxml_errors_ok ()) $output = "HTML code not valid.";
  138. libxml_use_internal_errors(false);
  139. return $output;
  140. }
  141. /**
  142. * This method is a direct copy-paste from
  143. * http://tmont.com/blargh/2010/1/string-format-in-php
  144. */
  145. function str_format($format) {
  146. $args = func_get_args();
  147. $format = array_shift($args);
  148. preg_match_all('/(?=\{)\{(\d+)\}(?!\})/', $format, $matches, PREG_OFFSET_CAPTURE);
  149. $offset = 0;
  150. foreach ($matches[1] as $data) {
  151. $i = $data[0];
  152. $format = substr_replace($format, @$args[$i], $offset + $data[1] - 1, 2 + strlen($i));
  153. $offset += strlen(@$args[$i]) - 2 - strlen($i);
  154. }
  155. return $format;
  156. }
  157. /**
  158. * Get all accepted languages from the browser and put them in a sorted array
  159. * languages id are normalized : fr-fr -> fr_FR
  160. * @return array of languages
  161. */
  162. function getAcceptLanguages() {
  163. $langs = array();
  164. if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
  165. // break up string into pieces (languages and q factors)
  166. $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  167. if (preg_match('/^(\w{2})-\w{2}$/', $accept, $matches)) {
  168. // Special fix for IE11 which send fr-FR and nothing else
  169. $accept = $accept . "," . $matches[1] . ";q=0.8";
  170. }
  171. preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $accept, $lang_parse);
  172. if (count($lang_parse[1])) {
  173. $langs = array();
  174. foreach ($lang_parse[1] as $lang) {
  175. // Format the language code (not standard among browsers)
  176. if (strlen($lang) == 5) {
  177. $lang = str_replace("-", "_", $lang);
  178. $splitted = preg_split("/_/", $lang);
  179. $lang = $splitted[0] . "_" . strtoupper($splitted[1]);
  180. }
  181. array_push($langs, $lang);
  182. }
  183. // create a list like "en" => 0.8
  184. $langs = array_combine($langs, $lang_parse[4]);
  185. // set default to 1 for any without q factor
  186. foreach ($langs as $lang => $val) {
  187. if ($val === '') $langs[$lang] = 1;
  188. }
  189. // sort list based on value
  190. arsort($langs, SORT_NUMERIC);
  191. }
  192. }
  193. return $langs;
  194. }
  195. /**
  196. * Find the best translation file possible based on the accepted languages
  197. * @return array of language and language file
  198. */
  199. function getLangAndTranslationFile() {
  200. global $config;
  201. $langs = array();
  202. $lang = "en";
  203. if (!empty($config['cops_language'])) {
  204. $lang = $config['cops_language'];
  205. }
  206. elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
  207. $langs = getAcceptLanguages();
  208. }
  209. //echo var_dump($langs);
  210. $lang_file = NULL;
  211. foreach ($langs as $language => $val) {
  212. $temp_file = dirname(__FILE__). '/lang/Localization_' . $language . '.json';
  213. if (file_exists($temp_file)) {
  214. $lang = $language;
  215. $lang_file = $temp_file;
  216. break;
  217. }
  218. }
  219. if (empty ($lang_file)) {
  220. $lang_file = dirname(__FILE__). '/lang/Localization_' . $lang . '.json';
  221. }
  222. return array($lang, $lang_file);
  223. }
  224. /**
  225. * This method is based on this page
  226. * http://www.mind-it.info/2010/02/22/a-simple-approach-to-localization-in-php/
  227. */
  228. function localize($phrase, $count=-1, $reset=false) {
  229. global $config;
  230. if ($count == 0)
  231. $phrase .= ".none";
  232. if ($count == 1)
  233. $phrase .= ".one";
  234. if ($count > 1)
  235. $phrase .= ".many";
  236. /* Static keyword is used to ensure the file is loaded only once */
  237. static $translations = NULL;
  238. if ($reset) {
  239. $translations = NULL;
  240. }
  241. /* If no instance of $translations has occured load the language file */
  242. if (is_null($translations)) {
  243. $lang_file_en = NULL;
  244. list ($lang, $lang_file) = getLangAndTranslationFile();
  245. if ($lang != "en") {
  246. $lang_file_en = dirname(__FILE__). '/lang/' . 'Localization_en.json';
  247. }
  248. $lang_file_content = file_get_contents($lang_file);
  249. /* Load the language file as a JSON object and transform it into an associative array */
  250. $translations = json_decode($lang_file_content, true);
  251. /* Clean the array of all unfinished translations */
  252. foreach (array_keys ($translations) as $key) {
  253. if (preg_match ("/^##TODO##/", $key)) {
  254. unset ($translations [$key]);
  255. }
  256. }
  257. if ($lang_file_en)
  258. {
  259. $lang_file_content = file_get_contents($lang_file_en);
  260. $translations_en = json_decode($lang_file_content, true);
  261. $translations = array_merge ($translations_en, $translations);
  262. }
  263. }
  264. if (array_key_exists ($phrase, $translations)) {
  265. return $translations[$phrase];
  266. }
  267. return $phrase;
  268. }
  269. function addURLParameter($urlParams, $paramName, $paramValue) {
  270. if (empty ($urlParams)) {
  271. $urlParams = "";
  272. }
  273. $start = "";
  274. if (preg_match ("#^\?(.*)#", $urlParams, $matches)) {
  275. $start = "?";
  276. $urlParams = $matches[1];
  277. }
  278. $params = array();
  279. parse_str($urlParams, $params);
  280. if (empty ($paramValue) && $paramValue != 0) {
  281. unset ($params[$paramName]);
  282. } else {
  283. $params[$paramName] = $paramValue;
  284. }
  285. return $start . http_build_query($params);
  286. }
  287. function useNormAndUp () {
  288. global $config;
  289. return $config ['cops_normalized_search'] == "1";
  290. }
  291. function normalizeUtf8String( $s) {
  292. include_once 'transliteration.php';
  293. return _transliteration_process($s);
  294. }
  295. function normAndUp ($s) {
  296. return mb_strtoupper (normalizeUtf8String($s), 'UTF-8');
  297. }
  298. class Link
  299. {
  300. const OPDS_THUMBNAIL_TYPE = "http://opds-spec.org/image/thumbnail";
  301. const OPDS_IMAGE_TYPE = "http://opds-spec.org/image";
  302. const OPDS_ACQUISITION_TYPE = "http://opds-spec.org/acquisition";
  303. const OPDS_NAVIGATION_TYPE = "application/atom+xml;profile=opds-catalog;kind=navigation";
  304. const OPDS_PAGING_TYPE = "application/atom+xml;profile=opds-catalog;kind=acquisition";
  305. public $href;
  306. public $type;
  307. public $rel;
  308. public $title;
  309. public $facetGroup;
  310. public $activeFacet;
  311. public function __construct($phref, $ptype, $prel = NULL, $ptitle = NULL, $pfacetGroup = NULL, $pactiveFacet = FALSE) {
  312. $this->href = $phref;
  313. $this->type = $ptype;
  314. $this->rel = $prel;
  315. $this->title = $ptitle;
  316. $this->facetGroup = $pfacetGroup;
  317. $this->activeFacet = $pactiveFacet;
  318. }
  319. public function hrefXhtml () {
  320. return $this->href;
  321. }
  322. public function getScriptName() {
  323. $parts = explode('/', $_SERVER["SCRIPT_NAME"]);
  324. return $parts[count($parts) - 1];
  325. }
  326. }
  327. class LinkNavigation extends Link
  328. {
  329. public function __construct($phref, $prel = NULL, $ptitle = NULL) {
  330. parent::__construct ($phref, Link::OPDS_NAVIGATION_TYPE, $prel, $ptitle);
  331. if (!is_null (GetUrlParam (DB))) $this->href = addURLParameter ($this->href, DB, GetUrlParam (DB));
  332. if (!preg_match ("#^\?(.*)#", $this->href) && !empty ($this->href)) $this->href = "?" . $this->href;
  333. if (preg_match ("/(bookdetail|getJSON).php/", parent::getScriptName())) {
  334. $this->href = "index.php" . $this->href;
  335. } else {
  336. $this->href = parent::getScriptName() . $this->href;
  337. }
  338. }
  339. }
  340. class LinkFacet extends Link
  341. {
  342. public function __construct($phref, $ptitle = NULL, $pfacetGroup = NULL, $pactiveFacet = FALSE) {
  343. parent::__construct ($phref, Link::OPDS_PAGING_TYPE, "http://opds-spec.org/facet", $ptitle, $pfacetGroup, $pactiveFacet);
  344. if (!is_null (GetUrlParam (DB))) $this->href = addURLParameter ($this->href, DB, GetUrlParam (DB));
  345. $this->href = parent::getScriptName() . $this->href;
  346. }
  347. }
  348. class Entry
  349. {
  350. public $title;
  351. public $id;
  352. public $content;
  353. public $numberOfElement;
  354. public $contentType;
  355. public $linkArray;
  356. public $localUpdated;
  357. public $className;
  358. private static $updated = NULL;
  359. public static $icons = array(
  360. Author::ALL_AUTHORS_ID => 'images/author.png',
  361. Serie::ALL_SERIES_ID => 'images/serie.png',
  362. Book::ALL_RECENT_BOOKS_ID => 'images/recent.png',
  363. Tag::ALL_TAGS_ID => 'images/tag.png',
  364. Language::ALL_LANGUAGES_ID => 'images/language.png',
  365. CustomColumn::ALL_CUSTOMS_ID => 'images/custom.png',
  366. Rating::ALL_RATING_ID => 'images/rating.png',
  367. "cops:books$" => 'images/allbook.png',
  368. "cops:books:letter" => 'images/allbook.png',
  369. Publisher::ALL_PUBLISHERS_ID => 'images/publisher.png'
  370. );
  371. public function getUpdatedTime () {
  372. if (!is_null ($this->localUpdated)) {
  373. return date (DATE_ATOM, $this->localUpdated);
  374. }
  375. if (is_null (self::$updated)) {
  376. self::$updated = time();
  377. }
  378. return date (DATE_ATOM, self::$updated);
  379. }
  380. public function getNavLink () {
  381. foreach ($this->linkArray as $link) {
  382. if ($link->type != Link::OPDS_NAVIGATION_TYPE) { continue; }
  383. return $link->hrefXhtml ();
  384. }
  385. return "#";
  386. }
  387. public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pclass = "", $pcount = 0) {
  388. global $config;
  389. $this->title = $ptitle;
  390. $this->id = $pid;
  391. $this->content = $pcontent;
  392. $this->contentType = $pcontentType;
  393. $this->linkArray = $plinkArray;
  394. $this->className = $pclass;
  395. $this->numberOfElement = $pcount;
  396. if ($config['cops_show_icons'] == 1)
  397. {
  398. foreach (self::$icons as $reg => $image)
  399. {
  400. if (preg_match ("/" . $reg . "/", $pid)) {
  401. array_push ($this->linkArray, new Link (getUrlWithVersion ($image), "image/png", Link::OPDS_THUMBNAIL_TYPE));
  402. break;
  403. }
  404. }
  405. }
  406. if (!is_null (GetUrlParam (DB))) $this->id = str_replace ("cops:", "cops:" . GetUrlParam (DB) . ":", $this->id);
  407. }
  408. }
  409. class EntryBook extends Entry
  410. {
  411. public $book;
  412. public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pbook) {
  413. parent::__construct ($ptitle, $pid, $pcontent, $pcontentType, $plinkArray);
  414. $this->book = $pbook;
  415. $this->localUpdated = $pbook->timestamp;
  416. }
  417. public function getCoverThumbnail () {
  418. foreach ($this->linkArray as $link) {
  419. if ($link->rel == Link::OPDS_THUMBNAIL_TYPE)
  420. return $link->hrefXhtml ();
  421. }
  422. return null;
  423. }
  424. public function getCover () {
  425. foreach ($this->linkArray as $link) {
  426. if ($link->rel == Link::OPDS_IMAGE_TYPE)
  427. return $link->hrefXhtml ();
  428. }
  429. return null;
  430. }
  431. }
  432. class Page
  433. {
  434. public $title;
  435. public $subtitle = "";
  436. public $authorName = "";
  437. public $authorUri = "";
  438. public $authorEmail = "";
  439. public $idPage;
  440. public $idGet;
  441. public $query;
  442. public $favicon;
  443. public $n;
  444. public $book;
  445. public $totalNumber = -1;
  446. public $entryArray = array();
  447. public static function getPage ($pageId, $id, $query, $n)
  448. {
  449. switch ($pageId) {
  450. case Base::PAGE_ALL_AUTHORS :
  451. return new PageAllAuthors ($id, $query, $n);
  452. case Base::PAGE_AUTHORS_FIRST_LETTER :
  453. return new PageAllAuthorsLetter ($id, $query, $n);
  454. case Base::PAGE_AUTHOR_DETAIL :
  455. return new PageAuthorDetail ($id, $query, $n);
  456. case Base::PAGE_ALL_TAGS :
  457. return new PageAllTags ($id, $query, $n);
  458. case Base::PAGE_TAG_DETAIL :
  459. return new PageTagDetail ($id, $query, $n);
  460. case Base::PAGE_ALL_LANGUAGES :
  461. return new PageAllLanguages ($id, $query, $n);
  462. case Base::PAGE_LANGUAGE_DETAIL :
  463. return new PageLanguageDetail ($id, $query, $n);
  464. case Base::PAGE_ALL_CUSTOMS :
  465. return new PageAllCustoms ($id, $query, $n);
  466. case Base::PAGE_CUSTOM_DETAIL :
  467. return new PageCustomDetail ($id, $query, $n);
  468. case Base::PAGE_ALL_RATINGS :
  469. return new PageAllRating ($id, $query, $n);
  470. case Base::PAGE_RATING_DETAIL :
  471. return new PageRatingDetail ($id, $query, $n);
  472. case Base::PAGE_ALL_SERIES :
  473. return new PageAllSeries ($id, $query, $n);
  474. case Base::PAGE_ALL_BOOKS :
  475. return new PageAllBooks ($id, $query, $n);
  476. case Base::PAGE_ALL_BOOKS_LETTER:
  477. return new PageAllBooksLetter ($id, $query, $n);
  478. case Base::PAGE_ALL_RECENT_BOOKS :
  479. return new PageRecentBooks ($id, $query, $n);
  480. case Base::PAGE_SERIE_DETAIL :
  481. return new PageSerieDetail ($id, $query, $n);
  482. case Base::PAGE_OPENSEARCH_QUERY :
  483. return new PageQueryResult ($id, $query, $n);
  484. case Base::PAGE_BOOK_DETAIL :
  485. return new PageBookDetail ($id, $query, $n);
  486. case Base::PAGE_ALL_PUBLISHERS:
  487. return new PageAllPublishers ($id, $query, $n);
  488. case Base::PAGE_PUBLISHER_DETAIL :
  489. return new PagePublisherDetail ($id, $query, $n);
  490. case Base::PAGE_ABOUT :
  491. return new PageAbout ($id, $query, $n);
  492. case Base::PAGE_CUSTOMIZE :
  493. return new PageCustomize ($id, $query, $n);
  494. default:
  495. $page = new Page ($id, $query, $n);
  496. $page->idPage = "cops:catalog";
  497. return $page;
  498. }
  499. }
  500. public function __construct($pid, $pquery, $pn) {
  501. global $config;
  502. $this->idGet = $pid;
  503. $this->query = $pquery;
  504. $this->n = $pn;
  505. $this->favicon = $config['cops_icon'];
  506. $this->authorName = empty($config['cops_author_name']) ? utf8_encode('Sébastien Lucas') : $config['cops_author_name'];
  507. $this->authorUri = empty($config['cops_author_uri']) ? 'http://blog.slucas.fr' : $config['cops_author_uri'];
  508. $this->authorEmail = empty($config['cops_author_email']) ? 'sebastien@slucas.fr' : $config['cops_author_email'];
  509. }
  510. public function InitializeContent ()
  511. {
  512. global $config;
  513. $this->title = $config['cops_title_default'];
  514. $this->subtitle = $config['cops_subtitle_default'];
  515. if (Base::noDatabaseSelected ()) {
  516. $i = 0;
  517. foreach (Base::getDbNameList () as $key) {
  518. $nBooks = Book::getBookCount ($i);
  519. array_push ($this->entryArray, new Entry ($key, "cops:{$i}:catalog",
  520. str_format (localize ("bookword", $nBooks), $nBooks), "text",
  521. array ( new LinkNavigation ("?" . DB . "={$i}")), "", $nBooks));
  522. $i++;
  523. Base::clearDb ();
  524. }
  525. } else {
  526. if (!in_array (PageQueryResult::SCOPE_AUTHOR, getCurrentOption ('ignored_categories'))) {
  527. array_push ($this->entryArray, Author::getCount());
  528. }
  529. if (!in_array (PageQueryResult::SCOPE_SERIES, getCurrentOption ('ignored_categories'))) {
  530. $series = Serie::getCount();
  531. if (!is_null ($series)) array_push ($this->entryArray, $series);
  532. }
  533. if (!in_array (PageQueryResult::SCOPE_PUBLISHER, getCurrentOption ('ignored_categories'))) {
  534. $publisher = Publisher::getCount();
  535. if (!is_null ($publisher)) array_push ($this->entryArray, $publisher);
  536. }
  537. if (!in_array (PageQueryResult::SCOPE_TAG, getCurrentOption ('ignored_categories'))) {
  538. $tags = Tag::getCount();
  539. if (!is_null ($tags)) array_push ($this->entryArray, $tags);
  540. }
  541. if (!in_array (PageQueryResult::SCOPE_RATING, getCurrentOption ('ignored_categories'))) {
  542. $rating = Rating::getCount();
  543. if (!is_null ($rating)) array_push ($this->entryArray, $rating);
  544. }
  545. if (!in_array ("language", getCurrentOption ('ignored_categories'))) {
  546. $languages = Language::getCount();
  547. if (!is_null ($languages)) array_push ($this->entryArray, $languages);
  548. }
  549. foreach ($config['cops_calibre_custom_column'] as $lookup) {
  550. $customId = CustomColumn::getCustomId ($lookup);
  551. if (!is_null ($customId)) {
  552. array_push ($this->entryArray, CustomColumn::getCount($customId));
  553. }
  554. }
  555. $this->entryArray = array_merge ($this->entryArray, Book::getCount());
  556. if (Base::isMultipleDatabaseEnabled ()) $this->title = Base::getDbName ();
  557. }
  558. }
  559. public function isPaginated ()
  560. {
  561. return (getCurrentOption ("max_item_per_page") != -1 &&
  562. $this->totalNumber != -1 &&
  563. $this->totalNumber > getCurrentOption ("max_item_per_page"));
  564. }
  565. public function getNextLink ()
  566. {
  567. $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . getQueryString ());
  568. if (($this->n) * getCurrentOption ("max_item_per_page") < $this->totalNumber) {
  569. return new LinkNavigation ($currentUrl . "&n=" . ($this->n + 1), "next", localize ("paging.next.alternate"));
  570. }
  571. return NULL;
  572. }
  573. public function getPrevLink ()
  574. {
  575. $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . getQueryString ());
  576. if ($this->n > 1) {
  577. return new LinkNavigation ($currentUrl . "&n=" . ($this->n - 1), "previous", localize ("paging.previous.alternate"));
  578. }
  579. return NULL;
  580. }
  581. public function getMaxPage ()
  582. {
  583. return ceil ($this->totalNumber / getCurrentOption ("max_item_per_page"));
  584. }
  585. public function containsBook ()
  586. {
  587. if (count ($this->entryArray) == 0) return false;
  588. if (get_class ($this->entryArray [0]) == "EntryBook") return true;
  589. return false;
  590. }
  591. }
  592. class PageAllAuthors extends Page
  593. {
  594. public function InitializeContent ()
  595. {
  596. $this->title = localize("authors.title");
  597. if (getCurrentOption ("author_split_first_letter") == 1) {
  598. $this->entryArray = Author::getAllAuthorsByFirstLetter();
  599. }
  600. else {
  601. $this->entryArray = Author::getAllAuthors();
  602. }
  603. $this->idPage = Author::ALL_AUTHORS_ID;
  604. }
  605. }
  606. class PageAllAuthorsLetter extends Page
  607. {
  608. public function InitializeContent ()
  609. {
  610. $this->idPage = Author::getEntryIdByLetter ($this->idGet);
  611. $this->entryArray = Author::getAuthorsByStartingLetter ($this->idGet);
  612. $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("authorword", count ($this->entryArray)), count ($this->entryArray)), $this->idGet);
  613. }
  614. }
  615. class PageAuthorDetail extends Page
  616. {
  617. public function InitializeContent ()
  618. {
  619. $author = Author::getAuthorById ($this->idGet);
  620. $this->idPage = $author->getEntryId ();
  621. $this->title = $author->name;
  622. list ($this->entryArray, $this->totalNumber) = Book::getBooksByAuthor ($this->idGet, $this->n);
  623. }
  624. }
  625. class PageAllPublishers extends Page
  626. {
  627. public function InitializeContent ()
  628. {
  629. $this->title = localize("publishers.title");
  630. $this->entryArray = Publisher::getAllPublishers();
  631. $this->idPage = Publisher::ALL_PUBLISHERS_ID;
  632. }
  633. }
  634. class PagePublisherDetail extends Page
  635. {
  636. public function InitializeContent ()
  637. {
  638. $publisher = Publisher::getPublisherById ($this->idGet);
  639. $this->title = $publisher->name;
  640. list ($this->entryArray, $this->totalNumber) = Book::getBooksByPublisher ($this->idGet, $this->n);
  641. $this->idPage = $publisher->getEntryId ();
  642. }
  643. }
  644. class PageAllTags extends Page
  645. {
  646. public function InitializeContent ()
  647. {
  648. $this->title = localize("tags.title");
  649. $this->entryArray = Tag::getAllTags();
  650. $this->idPage = Tag::ALL_TAGS_ID;
  651. }
  652. }
  653. class PageAllLanguages extends Page
  654. {
  655. public function InitializeContent ()
  656. {
  657. $this->title = localize("languages.title");
  658. $this->entryArray = Language::getAllLanguages();
  659. $this->idPage = Language::ALL_LANGUAGES_ID;
  660. }
  661. }
  662. class PageCustomDetail extends Page
  663. {
  664. public function InitializeContent ()
  665. {
  666. $customId = getURLParam ("custom", NULL);
  667. $custom = CustomColumn::getCustomById ($customId, $this->idGet);
  668. $this->idPage = $custom->getEntryId ();
  669. $this->title = $custom->name;
  670. list ($this->entryArray, $this->totalNumber) = Book::getBooksByCustom ($customId, $this->idGet, $this->n);
  671. }
  672. }
  673. class PageAllCustoms extends Page
  674. {
  675. public function InitializeContent ()
  676. {
  677. $customId = getURLParam ("custom", NULL);
  678. $this->title = CustomColumn::getAllTitle ($customId);
  679. $this->entryArray = CustomColumn::getAllCustoms($customId);
  680. $this->idPage = CustomColumn::getAllCustomsId ($customId);
  681. }
  682. }
  683. class PageTagDetail extends Page
  684. {
  685. public function InitializeContent ()
  686. {
  687. $tag = Tag::getTagById ($this->idGet);
  688. $this->idPage = $tag->getEntryId ();
  689. $this->title = $tag->name;
  690. list ($this->entryArray, $this->totalNumber) = Book::getBooksByTag ($this->idGet, $this->n);
  691. }
  692. }
  693. class PageLanguageDetail extends Page
  694. {
  695. public function InitializeContent ()
  696. {
  697. $language = Language::getLanguageById ($this->idGet);
  698. $this->idPage = $language->getEntryId ();
  699. $this->title = $language->lang_code;
  700. list ($this->entryArray, $this->totalNumber) = Book::getBooksByLanguage ($this->idGet, $this->n);
  701. }
  702. }
  703. class PageAllSeries extends Page
  704. {
  705. public function InitializeContent ()
  706. {
  707. $this->title = localize("series.title");
  708. $this->entryArray = Serie::getAllSeries();
  709. $this->idPage = Serie::ALL_SERIES_ID;
  710. }
  711. }
  712. class PageSerieDetail extends Page
  713. {
  714. public function InitializeContent ()
  715. {
  716. $serie = Serie::getSerieById ($this->idGet);
  717. $this->title = $serie->name;
  718. list ($this->entryArray, $this->totalNumber) = Book::getBooksBySeries ($this->idGet, $this->n);
  719. $this->idPage = $serie->getEntryId ();
  720. }
  721. }
  722. class PageAllRating extends Page
  723. {
  724. public function InitializeContent ()
  725. {
  726. $this->title = localize("ratings.title");
  727. $this->entryArray = Rating::getAllRatings();
  728. $this->idPage = Rating::ALL_RATING_ID;
  729. }
  730. }
  731. class PageRatingDetail extends Page
  732. {
  733. public function InitializeContent ()
  734. {
  735. $rating = Rating::getRatingById ($this->idGet);
  736. $this->idPage = $rating->getEntryId ();
  737. $this->title =str_format (localize ("ratingword", $rating->name/2), $rating->name/2);
  738. list ($this->entryArray, $this->totalNumber) = Book::getBooksByRating ($this->idGet, $this->n);
  739. }
  740. }
  741. class PageAllBooks extends Page
  742. {
  743. public function InitializeContent ()
  744. {
  745. $this->title = localize ("allbooks.title");
  746. if (getCurrentOption ("titles_split_first_letter") == 1) {
  747. $this->entryArray = Book::getAllBooks();
  748. }
  749. else {
  750. list ($this->entryArray, $this->totalNumber) = Book::getBooks ($this->n);
  751. }
  752. $this->idPage = Book::ALL_BOOKS_ID;
  753. }
  754. }
  755. class PageAllBooksLetter extends Page
  756. {
  757. public function InitializeContent ()
  758. {
  759. list ($this->entryArray, $this->totalNumber) = Book::getBooksByStartingLetter ($this->idGet, $this->n);
  760. $this->idPage = Book::getEntryIdByLetter ($this->idGet);
  761. $count = $this->totalNumber;
  762. if ($count == -1)
  763. $count = count ($this->entryArray);
  764. $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("bookword", $count), $count), $this->idGet);
  765. }
  766. }
  767. class PageRecentBooks extends Page
  768. {
  769. public function InitializeContent ()
  770. {
  771. $this->title = localize ("recent.title");
  772. $this->entryArray = Book::getAllRecentBooks ();
  773. $this->idPage = Book::ALL_RECENT_BOOKS_ID;
  774. }
  775. }
  776. class PageQueryResult extends Page
  777. {
  778. const SCOPE_TAG = "tag";
  779. const SCOPE_RATING = "rating";
  780. const SCOPE_SERIES = "series";
  781. const SCOPE_AUTHOR = "author";
  782. const SCOPE_BOOK = "book";
  783. const SCOPE_PUBLISHER = "publisher";
  784. private function useTypeahead () {
  785. return !is_null (getURLParam ("search"));
  786. }
  787. private function searchByScope ($scope, $limit = FALSE) {
  788. $n = $this->n;
  789. $numberPerPage = NULL;
  790. $queryNormedAndUp = $this->query;
  791. if (useNormAndUp ()) {
  792. $queryNormedAndUp = normAndUp ($this->query);
  793. }
  794. if ($limit) {
  795. $n = 1;
  796. $numberPerPage = 5;
  797. }
  798. switch ($scope) {
  799. case self::SCOPE_BOOK :
  800. $array = Book::getBooksByStartingLetter ('%' . $queryNormedAndUp, $n, NULL, $numberPerPage);
  801. break;
  802. case self::SCOPE_AUTHOR :
  803. $array = Author::getAuthorsForSearch ('%' . $queryNormedAndUp);
  804. break;
  805. case self::SCOPE_SERIES :
  806. $array = Serie::getAllSeriesByQuery ($queryNormedAndUp);
  807. break;
  808. case self::SCOPE_TAG :
  809. $array = Tag::getAllTagsByQuery ($queryNormedAndUp, $n, NULL, $numberPerPage);
  810. break;
  811. case self::SCOPE_PUBLISHER :
  812. $array = Publisher::getAllPublishersByQuery ($queryNormedAndUp);
  813. break;
  814. default:
  815. $array = Book::getBooksByQuery (
  816. array ("all" => "%" . $queryNormedAndUp . "%"), $n);
  817. }
  818. return $array;
  819. }
  820. public function doSearchByCategory () {
  821. $database = GetUrlParam (DB);
  822. $out = array ();
  823. $pagequery = Base::PAGE_OPENSEARCH_QUERY;
  824. $dbArray = array ("");
  825. $d = $database;
  826. $query = $this->query;
  827. // Special case when no databases were chosen, we search on all databases
  828. if (Base::noDatabaseSelected ()) {
  829. $dbArray = Base::getDbNameList ();
  830. $d = 0;
  831. }
  832. foreach ($dbArray as $key) {
  833. if (Base::noDatabaseSelected ()) {
  834. array_push ($this->entryArray, new Entry ($key, DB . ":query:{$d}",
  835. " ", "text",
  836. array ( new LinkNavigation ("?" . DB . "={$d}")), "tt-header"));
  837. Base::getDb ($d);
  838. }
  839. foreach (array (PageQueryResult::SCOPE_BOOK,
  840. PageQueryResult::SCOPE_AUTHOR,
  841. PageQueryResult::SCOPE_SERIES,
  842. PageQueryResult::SCOPE_TAG,
  843. PageQueryResult::SCOPE_PUBLISHER) as $key) {
  844. if (in_array($key, getCurrentOption ('ignored_categories'))) {
  845. continue;
  846. }
  847. $array = $this->searchByScope ($key, TRUE);
  848. $i = 0;
  849. if (count ($array) == 2 && is_array ($array [0])) {
  850. $total = $array [1];
  851. $array = $array [0];
  852. } else {
  853. $total = count($array);
  854. }
  855. if ($total > 0) {
  856. // Comment to help the perl i18n script
  857. // str_format (localize("bookword", count($array))
  858. // str_format (localize("authorword", count($array))
  859. // str_format (localize("seriesword", count($array))
  860. // str_format (localize("tagword", count($array))
  861. // str_format (localize("publisherword", count($array))
  862. array_push ($this->entryArray, new Entry (str_format (localize ("search.result.{$key}"), $this->query), DB . ":query:{$d}:{$key}",
  863. str_format (localize("{$key}word", $total), $total), "text",
  864. array ( new LinkNavigation ("?page={$pagequery}&query={$query}&db={$d}&scope={$key}")),
  865. Base::noDatabaseSelected () ? "" : "tt-header", $total));
  866. }
  867. if (!Base::noDatabaseSelected () && $this->useTypeahead ()) {
  868. foreach ($array as $entry) {
  869. array_push ($this->entryArray, $entry);
  870. $i++;
  871. if ($i > 4) { break; };
  872. }
  873. }
  874. }
  875. $d++;
  876. if (Base::noDatabaseSelected ()) {
  877. Base::clearDb ();
  878. }
  879. }
  880. return $out;
  881. }
  882. public function InitializeContent ()
  883. {
  884. $scope = getURLParam ("scope");
  885. if (empty ($scope)) {
  886. $this->title = str_format (localize ("search.result"), $this->query);
  887. } else {
  888. // Comment to help the perl i18n script
  889. // str_format (localize ("search.result.author"), $this->query)
  890. // str_format (localize ("search.result.tag"), $this->query)
  891. // str_format (localize ("search.result.series"), $this->query)
  892. // str_format (localize ("search.result.book"), $this->query)
  893. // str_format (localize ("search.result.publisher"), $this->query)
  894. $this->title = str_format (localize ("search.result.{$scope}"), $this->query);
  895. }
  896. $crit = "%" . $this->query . "%";
  897. // Special case when we are doing a search and no database is selected
  898. if (Base::noDatabaseSelected () && !$this->useTypeahead ()) {
  899. $i = 0;
  900. foreach (Base::getDbNameList () as $key) {
  901. Base::clearDb ();
  902. list ($array, $totalNumber) = Book::getBooksByQuery (array ("all" => $crit), 1, $i, 1);
  903. array_push ($this->entryArray, new Entry ($key, DB . ":query:{$i}",
  904. str_format (localize ("bookword", $totalNumber), $totalNumber), "text",
  905. array ( new LinkNavigation ("?" . DB . "={$i}&page=9&query=" . $this->query)), "", $totalNumber));
  906. $i++;
  907. }
  908. return;
  909. }
  910. if (empty ($scope)) {
  911. $this->doSearchByCategory ();
  912. return;
  913. }
  914. $array = $this->searchByScope ($scope);
  915. if (count ($array) == 2 && is_array ($array [0])) {
  916. list ($this->entryArray, $this->totalNumber) = $array;
  917. } else {
  918. $this->entryArray = $array;
  919. }
  920. }
  921. }
  922. class PageBookDetail extends Page
  923. {
  924. public function InitializeContent ()
  925. {
  926. $this->book = Book::getBookById ($this->idGet);
  927. $this->title = $this->book->title;
  928. }
  929. }
  930. class PageAbout extends Page
  931. {
  932. public function InitializeContent ()
  933. {
  934. $this->title = localize ("about.title");
  935. }
  936. }
  937. class PageCustomize extends Page
  938. {
  939. private function isChecked ($key, $testedValue = 1) {
  940. $value = getCurrentOption ($key);
  941. if (is_array ($value)) {
  942. if (in_array ($testedValue, $value)) {
  943. return "checked='checked'";
  944. }
  945. } else {
  946. if ($value == $testedValue) {
  947. return "checked='checked'";
  948. }
  949. }
  950. return "";
  951. }
  952. private function isSelected ($key, $value) {
  953. if (getCurrentOption ($key) == $value) {
  954. return "selected='selected'";
  955. }
  956. return "";
  957. }
  958. private function getStyleList () {
  959. $result = array ();
  960. foreach (glob ("templates/" . getCurrentTemplate () . "/styles/style-*.css") as $filename) {
  961. if (preg_match ('/styles\/style-(.*?)\.css/', $filename, $m)) {
  962. array_push ($result, $m [1]);
  963. }
  964. }
  965. return $result;
  966. }
  967. public function InitializeContent ()
  968. {
  969. $this->title = localize ("customize.title");
  970. $this->entryArray = array ();
  971. $ignoredBaseArray = array (PageQueryResult::SCOPE_AUTHOR,
  972. PageQueryResult::SCOPE_TAG,
  973. PageQueryResult::SCOPE_SERIES,
  974. PageQueryResult::SCOPE_PUBLISHER,
  975. PageQueryResult::SCOPE_RATING,
  976. "language");
  977. $content = "";
  978. array_push ($this->entryArray, new Entry ("Template", "",
  979. "<span style='cursor: pointer;' onclick='$.cookie(\"template\", \"bootstrap\", { expires: 365 });window.location=$(\".headleft\").attr(\"href\");'>Click to switch to Bootstrap</span>", "text",
  980. array ()));
  981. if (!preg_match("/(Kobo|Kindle\/3.0|EBRD1101)/", $_SERVER['HTTP_USER_AGENT'])) {
  982. $content .= '<select id="style" onchange="updateCookie (this);">';
  983. foreach ($this-> getStyleList () as $filename) {
  984. $content .= "<option value='{$filename}' " . $this->isSelected ("style", $filename) . ">{$filename}</option>";
  985. }
  986. $content .= '</select>';
  987. } else {
  988. foreach ($this-> getStyleList () as $filename) {
  989. $content .= "<input type='radio' onchange='updateCookieFromCheckbox (this);' id='style-{$filename}' name='style' value='{$filename}' " . $this->isChecked ("style", $filename) . " /><label for='style-{$filename}'> {$filename} </label>";
  990. }
  991. }
  992. array_push ($this->entryArray, new Entry (localize ("customize.style"), "",
  993. $content, "text",
  994. array ()));
  995. if (!useServerSideRendering ()) {
  996. $content = '<input type="checkbox" onchange="updateCookieFromCheckbox (this);" id="use_fancyapps" ' . $this->isChecked ("use_fancyapps") . ' />';
  997. array_push ($this->entryArray, new Entry (localize ("customize.fancybox"), "",
  998. $content, "text",
  999. array ()));
  1000. }
  1001. $content = '<input type="number" onchange="updateCookie (this);" id="max_item_per_page" value="' . getCurrentOption ("max_item_per_page") . '" min="-1" max="1200" pattern="^[-+]?[0-9]+$" />';
  1002. array_push ($this->entryArray, new Entry (localize ("customize.paging"), "",
  1003. $content, "text",
  1004. array ()));
  1005. $content = '<input type="text" onchange="updateCookie (this);" id="email" value="' . getCurrentOption ("email") . '" />';
  1006. array_push ($this->entryArray, new Entry (localize ("customize.email"), "",
  1007. $content, "text",
  1008. array ()));
  1009. $content = '<input type="checkbox" onchange="updateCookieFromCheckbox (this);" id="html_tag_filter" ' . $this->isChecked ("html_tag_filter") . ' />';
  1010. array_push ($this->entryArray, new Entry (localize ("customize.filter"), "",
  1011. $content, "text",
  1012. array ()));
  1013. $content = "";
  1014. foreach ($ignoredBaseArray as $key) {
  1015. $keyPlural = preg_replace ('/(ss)$/', 's', $key . "s");
  1016. $content .= '<input type="checkbox" name="ignored_categories[]" onchange="updateCookieFromCheckboxGroup (this);" id="ignored_categories_' . $key . '" ' . $this->isChecked ("ignored_categories", $key) . ' > ' . localize ("{$keyPlural}.title") . '</input> ';
  1017. }
  1018. array_push ($this->entryArray, new Entry (localize ("customize.ignored"), "",
  1019. $content, "text",
  1020. array ()));
  1021. }
  1022. }
  1023. abstract class Base
  1024. {
  1025. const PAGE_INDEX = "index";
  1026. const PAGE_ALL_AUTHORS = "1";
  1027. const PAGE_AUTHORS_FIRST_LETTER = "2";
  1028. const PAGE_AUTHOR_DETAIL = "3";
  1029. const PAGE_ALL_BOOKS = "4";
  1030. const PAGE_ALL_BOOKS_LETTER = "5";
  1031. const PAGE_ALL_SERIES = "6";
  1032. const PAGE_SERIE_DETAIL = "7";
  1033. const PAGE_OPENSEARCH = "8";
  1034. const PAGE_OPENSEARCH_QUERY = "9";
  1035. const PAGE_ALL_RECENT_BOOKS = "10";
  1036. const PAGE_ALL_TAGS = "11";
  1037. const PAGE_TAG_DETAIL = "12";
  1038. const PAGE_BOOK_DETAIL = "13";
  1039. const PAGE_ALL_CUSTOMS = "14";
  1040. const PAGE_CUSTOM_DETAIL = "15";
  1041. const PAGE_ABOUT = "16";
  1042. const PAGE_ALL_LANGUAGES = "17";
  1043. const PAGE_LANGUAGE_DETAIL = "18";
  1044. const PAGE_CUSTOMIZE = "19";
  1045. const PAGE_ALL_PUBLISHERS = "20";
  1046. const PAGE_PUBLISHER_DETAIL = "21";
  1047. const PAGE_ALL_RATINGS = "22";
  1048. const PAGE_RATING_DETAIL = "23";
  1049. const COMPATIBILITY_XML_ALDIKO = "aldiko";
  1050. private static $db = NULL;
  1051. public static function isMultipleDatabaseEnabled () {
  1052. global $config;
  1053. return is_array ($config['calibre_directory']);
  1054. }
  1055. public static function useAbsolutePath () {
  1056. global $config;
  1057. $path = self::getDbDirectory();
  1058. return preg_match ('/^\//', $path) || // Linux /
  1059. preg_match ('/^\w\:/', $path); // Windows X:
  1060. }
  1061. public static function noDatabaseSelected () {
  1062. return self::isMultipleDatabaseEnabled () && is_null (GetUrlParam (DB));
  1063. }
  1064. public static function getDbList () {
  1065. global $config;
  1066. if (self::isMultipleDatabaseEnabled ()) {
  1067. return $config['calibre_directory'];
  1068. } else {
  1069. return array ("" => $config['calibre_directory']);
  1070. }
  1071. }
  1072. public static function getDbNameList () {
  1073. global $config;
  1074. if (self::isMultipleDatabaseEnabled ()) {
  1075. return array_keys ($config['calibre_directory']);
  1076. } else {
  1077. return array ("");
  1078. }
  1079. }
  1080. public static function getDbName ($database = NULL) {
  1081. global $config;
  1082. if (self::isMultipleDatabaseEnabled ()) {
  1083. if (is_null ($database)) $database = GetUrlParam (DB, 0);
  1084. if (!is_null($database) && !preg_match('/^\d+$/', $database)) {
  1085. return self::error ($database);
  1086. }
  1087. $array = array_keys ($config['calibre_directory']);
  1088. return $array[$database];
  1089. }
  1090. return "";
  1091. }
  1092. public static function getDbDirectory ($database = NULL) {
  1093. global $config;
  1094. if (self::isMultipleDatabaseEnabled ()) {
  1095. if (is_null ($database)) $database = GetUrlParam (DB, 0);
  1096. if (!is_null($database) && !preg_match('/^\d+$/', $database)) {
  1097. return self::error ($database);
  1098. }
  1099. $array = array_values ($config['calibre_directory']);
  1100. return $array[$database];
  1101. }
  1102. return $config['calibre_directory'];
  1103. }
  1104. public static function getDbFileName ($database = NULL) {
  1105. return self::getDbDirectory ($database) .'metadata.db';
  1106. }
  1107. private static function error ($database) {
  1108. if (php_sapi_name() != "cli") {
  1109. header("location: checkconfig.php?err=1");
  1110. }
  1111. throw new Exception("Database <{$database}> not found.");
  1112. }
  1113. public static function getDb ($database = NULL) {
  1114. if (is_null (self::$db)) {
  1115. try {
  1116. if (is_readable (self::getDbFileName ($database))) {
  1117. self::$db = new PDO('sqlite:'. self::getDbFileName ($database));
  1118. if (useNormAndUp ()) {
  1119. self::$db->sqliteCreateFunction ('normAndUp', 'normAndUp', 1);
  1120. }
  1121. } else {
  1122. self::error ($database);
  1123. }
  1124. } catch (Exception $e) {
  1125. self::error ($database);
  1126. }
  1127. }
  1128. return self::$db;
  1129. }
  1130. public static function checkDatabaseAvailability () {
  1131. if (self::noDatabaseSelected ()) {
  1132. for ($i = 0; $i < count (self::getDbList ()); $i++) {
  1133. self::getDb ($i);
  1134. self::clearDb ();
  1135. }
  1136. } else {
  1137. self::getDb ();
  1138. }
  1139. return true;
  1140. }
  1141. public static function clearDb () {
  1142. self::$db = NULL;
  1143. }
  1144. public static function executeQuerySingle ($query, $database = NULL) {
  1145. return self::getDb ($database)->query($query)->fetchColumn();
  1146. }
  1147. public static function getCountGeneric($table, $id, $pageId, $numberOfString = NULL) {
  1148. if (!$numberOfString) {
  1149. $numberOfString = $table . ".alphabetical";
  1150. }
  1151. $count = self::executeQuerySingle ('select count(*) from ' . $table);
  1152. if ($count == 0) return NULL;
  1153. $entry = new Entry (localize($table . ".title"), $id,
  1154. str_format (localize($numberOfString, $count), $count), "text",
  1155. array ( new LinkNavigation ("?page=".$pageId)), "", $count);
  1156. return $entry;
  1157. }
  1158. public static function getEntryArrayWithBookNumber ($query, $columns, $params, $category) {
  1159. list (, $result) = self::executeQuery ($query, $columns, "", $params, -1);
  1160. $entryArray = array();
  1161. while ($post = $result->fetchObject ())
  1162. {
  1163. $instance = new $category ($post);
  1164. if (property_exists($post, "sort")) {
  1165. $title = $post->sort;
  1166. } else {
  1167. $title = $post->name;
  1168. }
  1169. array_push ($entryArray, new Entry ($title, $instance->getEntryId (),
  1170. str_format (localize("bookword", $post->count), $post->count), "text",
  1171. array ( new LinkNavigation ($instance->getUri ())), "", $post->count));
  1172. }
  1173. return $entryArray;
  1174. }
  1175. public static function executeQuery($query, $columns, $filter, $params, $n, $database = NULL, $numberPerPage = NULL) {
  1176. $totalResult = -1;
  1177. if (useNormAndUp ()) {
  1178. $query = preg_replace("/upper/", "normAndUp", $query);
  1179. $columns = preg_replace("/upper/", "normAndUp", $columns);
  1180. }
  1181. if (is_null ($numberPerPage)) {
  1182. $numberPerPage = getCurrentOption ("max_item_per_page");
  1183. }
  1184. if ($numberPerPage != -1 && $n != -1)
  1185. {
  1186. // First check total number of results
  1187. $result = self::getDb ($database)->prepare (str_format ($query, "count(*)", $filter));
  1188. $result->execute ($params);
  1189. $totalResult = $result->fetchColumn ();
  1190. // Next modify the query and params
  1191. $query .= " limit ?, ?";
  1192. array_push ($params, ($n - 1) * $numberPerPage, $numberPerPage);
  1193. }
  1194. $result = self::getDb ($database)->prepare(str_format ($query, $columns, $filter));
  1195. $result->execute ($params);
  1196. return array ($totalResult, $result);
  1197. }
  1198. }