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.

277 lines
9.7KB

  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. class Link
  9. {
  10. public $href;
  11. public $type;
  12. public $rel;
  13. public $title;
  14. public function __construct($phref, $ptype, $prel = NULL, $ptitle = NULL) {
  15. $this->href = $phref;
  16. $this->type = $ptype;
  17. $this->rel = $prel;
  18. $this->title = $ptitle;
  19. }
  20. public function render ($xml) {
  21. $xml->startElement ("link");
  22. $xml->writeAttribute ("href", $this->href);
  23. $xml->writeAttribute ("type", $this->type);
  24. if (!is_null ($this->rel)) {
  25. $xml->writeAttribute ("rel", $this->rel);
  26. }
  27. if (!is_null ($this->title)) {
  28. $xml->writeAttribute ("title", $this->title);
  29. }
  30. $xml->endElement ();
  31. }
  32. }
  33. class LinkNavigation extends Link
  34. {
  35. const OPDS_NAVIGATION_TYPE = "application/atom+xml;profile=opds-catalog;kind=navigation";
  36. public function __construct($phref, $prel = NULL, $ptitle = NULL) {
  37. parent::__construct ($phref, self::OPDS_NAVIGATION_TYPE, $prel, $ptitle);
  38. }
  39. }
  40. class Entry
  41. {
  42. public $title;
  43. public $id;
  44. public $content;
  45. public $contentType;
  46. public $linkArray;
  47. public $localUpdated;
  48. private static $updated = NULL;
  49. public function getUpdatedTime () {
  50. if (!is_null ($this->localUpdated)) {
  51. return date (DATE_ATOM, $this->localUpdated);
  52. }
  53. if (is_null (self::$updated)) {
  54. self::$updated = time();
  55. }
  56. return date (DATE_ATOM, self::$updated);
  57. }
  58. public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray) {
  59. $this->title = $ptitle;
  60. $this->id = $pid;
  61. $this->content = $pcontent;
  62. $this->contentType = $pcontentType;
  63. $this->linkArray = $plinkArray;
  64. }
  65. public function renderContent ($xml) {
  66. $xml->startElement ("title");
  67. $xml->text ($this->title);
  68. $xml->endElement ();
  69. $xml->startElement ("updated");
  70. $xml->text (self::getUpdatedTime ());
  71. $xml->endElement ();
  72. $xml->startElement ("id");
  73. $xml->text ($this->id);
  74. $xml->endElement ();
  75. $xml->startElement ("content");
  76. $xml->writeAttribute ("type", $this->contentType);
  77. if ($this->contentType == "text") {
  78. $xml->text ($this->content);
  79. } else {
  80. $xml->writeRaw ($this->content);
  81. }
  82. $xml->endElement ();
  83. foreach ($this->linkArray as $link) {
  84. $link->render ($xml);
  85. }
  86. }
  87. public function render ($xml) {
  88. $xml->startElement ("entry");
  89. self::renderContent ($xml);
  90. $xml->endElement ();
  91. }
  92. }
  93. class EntryBook extends Entry
  94. {
  95. public $book;
  96. public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pbook) {
  97. parent::__construct ($ptitle, $pid, $pcontent, $pcontentType, $plinkArray);
  98. $this->book = $pbook;
  99. $this->localUpdated = $pbook->timestamp;
  100. }
  101. public function renderContent ($xml) {
  102. parent::renderContent ($xml);
  103. foreach ($this->book->getAuthors () as $author) {
  104. $xml->startElement ("author");
  105. $xml->startElement ("name");
  106. $xml->text ($author->name);
  107. $xml->endElement ();
  108. $xml->startElement ("uri");
  109. $xml->text ($author->getUri ());
  110. $xml->endElement ();
  111. $xml->endElement ();
  112. }
  113. foreach ($this->book->getTags () as $category) {
  114. $xml->startElement ("category");
  115. $xml->writeAttribute ("term", $category);
  116. $xml->writeAttribute ("label", $category);
  117. $xml->endElement ();
  118. }
  119. if (!is_null ($this->book->pubdate)) {
  120. $xml->startElement ("dcterms:issued");
  121. $xml->text (date ("Y-m-d", $this->book->pubdate));
  122. $xml->endElement ();
  123. }
  124. }
  125. /* Polymorphism is strange with PHP */
  126. public function render ($xml) {
  127. $xml->startElement ("entry");
  128. self::renderContent ($xml);
  129. $xml->endElement ();
  130. }
  131. }
  132. abstract class Base
  133. {
  134. const PAGE_INDEX = "index";
  135. const PAGE_ALL_AUTHORS = "1";
  136. const PAGE_AUTHORS_FIRST_LETTER = "2";
  137. const PAGE_AUTHOR_DETAIL = "3";
  138. const PAGE_ALL_BOOKS = "4";
  139. const PAGE_ALL_BOOKS_LETTER = "5";
  140. const PAGE_ALL_SERIES = "6";
  141. const PAGE_SERIE_DETAIL = "7";
  142. const PAGE_OPENSEARCH = "8";
  143. const PAGE_OPENSEARCH_QUERY = "9";
  144. const PAGE_ALL_RECENT_BOOKS = "10";
  145. const COMPATIBILITY_XML_ALDIKO = "aldiko";
  146. private static $db = NULL;
  147. private static $xmlStream = NULL;
  148. private static $updated = NULL;
  149. public static function getUpdatedTime () {
  150. if (is_null (self::$updated)) {
  151. self::$updated = time();
  152. }
  153. return date (DATE_ATOM, self::$updated);
  154. }
  155. public static function getDb () {
  156. global $config;
  157. if (is_null (self::$db)) {
  158. try {
  159. self::$db = new PDO('sqlite:'. $config['calibre_directory'] .'metadata.db');
  160. } catch (Exception $e) {
  161. echo $e;
  162. die($e);
  163. }
  164. }
  165. return self::$db;
  166. }
  167. public static function getXmlStream () {
  168. if (is_null (self::$xmlStream)) {
  169. self::$xmlStream = new XMLWriter();
  170. self::$xmlStream->openMemory();
  171. self::$xmlStream->setIndent (true);
  172. }
  173. return self::$xmlStream;
  174. }
  175. public static function getOpenSearch () {
  176. $xml = new XMLWriter ();
  177. $xml->openMemory ();
  178. $xml->setIndent (true);
  179. $xml->startDocument('1.0','UTF-8');
  180. $xml->startElement ("OpenSearchDescription");
  181. $xml->startElement ("ShortName");
  182. $xml->text ("My catalog");
  183. $xml->endElement ();
  184. $xml->startElement ("InputEncoding");
  185. $xml->text ("UTF-8");
  186. $xml->endElement ();
  187. $xml->startElement ("OutputEncoding");
  188. $xml->text ("UTF-8");
  189. $xml->endElement ();
  190. $xml->startElement ("Image");
  191. $xml->text ("favicon.ico");
  192. $xml->endElement ();
  193. $xml->startElement ("Url");
  194. $xml->writeAttribute ("type", 'application/atom+xml');
  195. $xml->writeAttribute ("template", 'feed.php?page=' . self::PAGE_OPENSEARCH_QUERY . '&query={searchTerms}');
  196. $xml->endElement ();
  197. $xml->endElement ();
  198. $xml->endDocument();
  199. return $xml->outputMemory(true);
  200. }
  201. public static function startXmlDocument ($title) {
  202. self::getXmlStream ()->startDocument('1.0','UTF-8');
  203. self::getXmlStream ()->startElement ("feed");
  204. self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom");
  205. self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
  206. self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog");
  207. self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/");
  208. self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/");
  209. self::getXmlStream ()->startElement ("title");
  210. self::getXmlStream ()->text ($title);
  211. self::getXmlStream ()->endElement ();
  212. self::getXmlStream ()->startElement ("id");
  213. self::getXmlStream ()->text ($_SERVER['REQUEST_URI']);
  214. self::getXmlStream ()->endElement ();
  215. self::getXmlStream ()->startElement ("updated");
  216. self::getXmlStream ()->text (self::getUpdatedTime ());
  217. self::getXmlStream ()->endElement ();
  218. self::getXmlStream ()->startElement ("icon");
  219. self::getXmlStream ()->text ("favicon.ico");
  220. self::getXmlStream ()->endElement ();
  221. self::getXmlStream ()->startElement ("author");
  222. self::getXmlStream ()->startElement ("name");
  223. self::getXmlStream ()->text (utf8_encode ("Sébastien Lucas"));
  224. self::getXmlStream ()->endElement ();
  225. self::getXmlStream ()->startElement ("uri");
  226. self::getXmlStream ()->text ("http://blog.slucas.fr");
  227. self::getXmlStream ()->endElement ();
  228. self::getXmlStream ()->startElement ("email");
  229. self::getXmlStream ()->text ("sebastien@slucas.fr");
  230. self::getXmlStream ()->endElement ();
  231. self::getXmlStream ()->endElement ();
  232. $link = new LinkNavigation ("feed.php", "start", "Home");
  233. $link->render (self::getXmlStream ());
  234. $link = new LinkNavigation ($_SERVER['REQUEST_URI'], "self");
  235. $link->render (self::getXmlStream ());
  236. $link = new Link ("feed.php?page=" . self::PAGE_OPENSEARCH, "application/opensearchdescription+xml", "search", "Search here");
  237. $link->render (self::getXmlStream ());
  238. $link = new LinkNavigation ("feed.php?page=7&id=9", "http://opds-spec.org/shelf", "Biblio");
  239. $link->render (self::getXmlStream ());
  240. }
  241. public static function addEntryClass ($entry) {
  242. $entry->render (self::getXmlStream ());
  243. }
  244. public static function endXmlDocument () {
  245. self::getXmlStream ()->endElement ();
  246. self::getXmlStream ()->endDocument ();
  247. return self::getXmlStream ()->outputMemory(true);
  248. }
  249. }
  250. ?>