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.

268 lines
12KB

  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. require_once ("base.php");
  9. class OPDSRenderer
  10. {
  11. const PAGE_OPENSEARCH = "8";
  12. const PAGE_OPENSEARCH_QUERY = "9";
  13. private $xmlStream = NULL;
  14. private $updated = NULL;
  15. private function getUpdatedTime () {
  16. if (is_null ($this->updated)) {
  17. $this->updated = time();
  18. }
  19. return date (DATE_ATOM, $this->updated);
  20. }
  21. private function getXmlStream () {
  22. if (is_null ($this->xmlStream)) {
  23. $this->xmlStream = new XMLWriter();
  24. $this->xmlStream->openMemory();
  25. $this->xmlStream->setIndent (true);
  26. }
  27. return $this->xmlStream;
  28. }
  29. public function getOpenSearch () {
  30. global $config;
  31. $xml = new XMLWriter ();
  32. $xml->openMemory ();
  33. $xml->setIndent (true);
  34. $xml->startDocument('1.0','UTF-8');
  35. $xml->startElement ("OpenSearchDescription");
  36. $xml->writeAttribute ("xmlns", "http://a9.com/-/spec/opensearch/1.1/");
  37. $xml->startElement ("ShortName");
  38. $xml->text ("My catalog");
  39. $xml->endElement ();
  40. $xml->startElement ("Description");
  41. $xml->text ("Search for ebooks");
  42. $xml->endElement ();
  43. $xml->startElement ("InputEncoding");
  44. $xml->text ("UTF-8");
  45. $xml->endElement ();
  46. $xml->startElement ("OutputEncoding");
  47. $xml->text ("UTF-8");
  48. $xml->endElement ();
  49. $xml->startElement ("Image");
  50. $xml->writeAttribute ("type", "image/x-icon");
  51. $xml->writeAttribute ("width", "16");
  52. $xml->writeAttribute ("height", "16");
  53. $xml->text ($config['cops_icon']);
  54. $xml->endElement ();
  55. $xml->startElement ("Url");
  56. $xml->writeAttribute ("type", 'application/atom+xml');
  57. $urlparam = "?query={searchTerms}";
  58. if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB));
  59. $urlparam = str_replace ("%7B", "{", $urlparam);
  60. $urlparam = str_replace ("%7D", "}", $urlparam);
  61. $xml->writeAttribute ("template", $config['cops_full_url'] . 'feed.php' . $urlparam);
  62. $xml->endElement ();
  63. $xml->startElement ("Query");
  64. $xml->writeAttribute ("role", "example");
  65. $xml->writeAttribute ("searchTerms", "robot");
  66. $xml->endElement ();
  67. $xml->endElement ();
  68. $xml->endDocument();
  69. return $xml->outputMemory(true);
  70. }
  71. private function startXmlDocument ($page) {
  72. global $config;
  73. self::getXmlStream ()->startDocument('1.0','UTF-8');
  74. self::getXmlStream ()->startElement ("feed");
  75. self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom");
  76. self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
  77. self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog");
  78. self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/");
  79. self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/");
  80. self::getXmlStream ()->startElement ("title");
  81. self::getXmlStream ()->text ($page->title);
  82. self::getXmlStream ()->endElement ();
  83. if ($page->subtitle != "")
  84. {
  85. self::getXmlStream ()->startElement ("subtitle");
  86. self::getXmlStream ()->text ($page->subtitle);
  87. self::getXmlStream ()->endElement ();
  88. }
  89. self::getXmlStream ()->startElement ("id");
  90. if ($page->idPage)
  91. {
  92. $idPage = $page->idPage;
  93. if (!is_null (GetUrlParam (DB))) $idPage = GetUrlParam (DB) . ":" . $idPage;
  94. self::getXmlStream ()->text ($idPage);
  95. }
  96. else
  97. {
  98. self::getXmlStream ()->text ($_SERVER['REQUEST_URI']);
  99. }
  100. self::getXmlStream ()->endElement ();
  101. self::getXmlStream ()->startElement ("updated");
  102. self::getXmlStream ()->text (self::getUpdatedTime ());
  103. self::getXmlStream ()->endElement ();
  104. self::getXmlStream ()->startElement ("icon");
  105. self::getXmlStream ()->text ($page->favicon);
  106. self::getXmlStream ()->endElement ();
  107. self::getXmlStream ()->startElement ("author");
  108. self::getXmlStream ()->startElement ("name");
  109. self::getXmlStream ()->text (utf8_encode ("Sébastien Lucas"));
  110. self::getXmlStream ()->endElement ();
  111. self::getXmlStream ()->startElement ("uri");
  112. self::getXmlStream ()->text ("http://blog.slucas.fr");
  113. self::getXmlStream ()->endElement ();
  114. self::getXmlStream ()->startElement ("email");
  115. self::getXmlStream ()->text ("sebastien@slucas.fr");
  116. self::getXmlStream ()->endElement ();
  117. self::getXmlStream ()->endElement ();
  118. $link = new LinkNavigation ("", "start", "Home");
  119. self::renderLink ($link);
  120. $link = new LinkNavigation ("?" . $_SERVER['QUERY_STRING'], "self");
  121. self::renderLink ($link);
  122. $urlparam = "?page=" . self::PAGE_OPENSEARCH;
  123. if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB));
  124. if ($config['cops_generate_invalid_opds_stream'] == 0 || preg_match("/(MantanoReader|FBReader)/", $_SERVER['HTTP_USER_AGENT'])) {
  125. // Good and compliant way of handling search
  126. $link = new Link ("feed.php" . $urlparam, "application/opensearchdescription+xml", "search", "Search here");
  127. }
  128. else
  129. {
  130. // Bad way, will be removed when OPDS client are fixed
  131. $link = new Link ($config['cops_full_url'] . 'feed.php' . $urlparam, "application/atom+xml", "search", "Search here");
  132. }
  133. self::renderLink ($link);
  134. if ($page->containsBook () && !is_null ($config['cops_books_filter']) && count ($config['cops_books_filter']) > 0) {
  135. $Urlfilter = getURLParam ("tag", "");
  136. foreach ($config['cops_books_filter'] as $lib => $filter) {
  137. $link = new LinkFacet ("?" . addURLParameter ($_SERVER['QUERY_STRING'], "tag", $filter), $lib, localize ("tagword.title"), $filter == $Urlfilter);
  138. self::renderLink ($link);
  139. }
  140. }
  141. }
  142. private function endXmlDocument () {
  143. self::getXmlStream ()->endElement ();
  144. self::getXmlStream ()->endDocument ();
  145. return self::getXmlStream ()->outputMemory(true);
  146. }
  147. private function renderLink ($link) {
  148. self::getXmlStream ()->startElement ("link");
  149. self::getXmlStream ()->writeAttribute ("href", $link->href);
  150. self::getXmlStream ()->writeAttribute ("type", $link->type);
  151. if (!is_null ($link->rel)) {
  152. self::getXmlStream ()->writeAttribute ("rel", $link->rel);
  153. }
  154. if (!is_null ($link->title)) {
  155. self::getXmlStream ()->writeAttribute ("title", $link->title);
  156. }
  157. if (!is_null ($link->facetGroup)) {
  158. self::getXmlStream ()->writeAttribute ("opds:facetGroup", $link->facetGroup);
  159. }
  160. if ($link->activeFacet) {
  161. self::getXmlStream ()->writeAttribute ("opds:activeFacet", "true");
  162. }
  163. self::getXmlStream ()->endElement ();
  164. }
  165. private function renderEntry ($entry) {
  166. self::getXmlStream ()->startElement ("title");
  167. self::getXmlStream ()->text ($entry->title);
  168. self::getXmlStream ()->endElement ();
  169. self::getXmlStream ()->startElement ("updated");
  170. self::getXmlStream ()->text (self::getUpdatedTime ());
  171. self::getXmlStream ()->endElement ();
  172. self::getXmlStream ()->startElement ("id");
  173. self::getXmlStream ()->text ($entry->id);
  174. self::getXmlStream ()->endElement ();
  175. self::getXmlStream ()->startElement ("content");
  176. self::getXmlStream ()->writeAttribute ("type", $entry->contentType);
  177. if ($entry->contentType == "text") {
  178. self::getXmlStream ()->text ($entry->content);
  179. } else {
  180. self::getXmlStream ()->writeRaw ($entry->content);
  181. }
  182. self::getXmlStream ()->endElement ();
  183. foreach ($entry->linkArray as $link) {
  184. self::renderLink ($link);
  185. }
  186. if (get_class ($entry) != "EntryBook") {
  187. return;
  188. }
  189. foreach ($entry->book->getAuthors () as $author) {
  190. self::getXmlStream ()->startElement ("author");
  191. self::getXmlStream ()->startElement ("name");
  192. self::getXmlStream ()->text ($author->name);
  193. self::getXmlStream ()->endElement ();
  194. self::getXmlStream ()->startElement ("uri");
  195. self::getXmlStream ()->text ("feed.php" . $author->getUri ());
  196. self::getXmlStream ()->endElement ();
  197. self::getXmlStream ()->endElement ();
  198. }
  199. foreach ($entry->book->getTags () as $category) {
  200. self::getXmlStream ()->startElement ("category");
  201. self::getXmlStream ()->writeAttribute ("term", $category->name);
  202. self::getXmlStream ()->writeAttribute ("label", $category->name);
  203. self::getXmlStream ()->endElement ();
  204. }
  205. if ($entry->book->getPubDate () != "") {
  206. self::getXmlStream ()->startElement ("dcterms:issued");
  207. self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate));
  208. self::getXmlStream ()->endElement ();
  209. self::getXmlStream ()->startElement ("published");
  210. self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate) . "T08:08:08Z");
  211. self::getXmlStream ()->endElement ();
  212. }
  213. $lang = $entry->book->getLanguages ();
  214. if (!empty ($lang)) {
  215. self::getXmlStream ()->startElement ("dcterms:language");
  216. self::getXmlStream ()->text ($lang);
  217. self::getXmlStream ()->endElement ();
  218. }
  219. }
  220. public function render ($page) {
  221. global $config;
  222. self::startXmlDocument ($page);
  223. if ($page->isPaginated ())
  224. {
  225. self::getXmlStream ()->startElement ("opensearch:totalResults");
  226. self::getXmlStream ()->text ($page->totalNumber);
  227. self::getXmlStream ()->endElement ();
  228. self::getXmlStream ()->startElement ("opensearch:itemsPerPage");
  229. self::getXmlStream ()->text ($config['cops_max_item_per_page']);
  230. self::getXmlStream ()->endElement ();
  231. self::getXmlStream ()->startElement ("opensearch:startIndex");
  232. self::getXmlStream ()->text (($page->n - 1) * $config['cops_max_item_per_page'] + 1);
  233. self::getXmlStream ()->endElement ();
  234. $prevLink = $page->getPrevLink ();
  235. $nextLink = $page->getNextLink ();
  236. if (!is_null ($prevLink)) {
  237. self::renderLink ($prevLink);
  238. }
  239. if (!is_null ($nextLink)) {
  240. self::renderLink ($nextLink);
  241. }
  242. }
  243. foreach ($page->entryArray as $entry) {
  244. self::getXmlStream ()->startElement ("entry");
  245. self::renderEntry ($entry);
  246. self::getXmlStream ()->endElement ();
  247. }
  248. return self::endXmlDocument ();
  249. }
  250. }