3  * COPS (Calibre OPDS PHP Server) class file
 
   5  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 
   6  * @author     Sébastien Lucas <sebastien@slucas.fr>
 
   9 require_once ("base.php");
 
  13     const PAGE_OPENSEARCH = "8";
 
  14     const PAGE_OPENSEARCH_QUERY = "9";
 
  16     private $xmlStream = NULL;
 
  17     private $updated = NULL;
 
  19     private function getUpdatedTime () {
 
  20         if (is_null ($this->updated)) {
 
  21             $this->updated = time();
 
  23         return date (DATE_ATOM, $this->updated);
 
  26     private function getXmlStream () {
 
  27         if (is_null ($this->xmlStream)) {
 
  28             $this->xmlStream = new XMLWriter();
 
  29             $this->xmlStream->openMemory();
 
  30             $this->xmlStream->setIndent (true);
 
  32         return $this->xmlStream;
 
  35     public function getOpenSearch () {
 
  37         $xml = new XMLWriter ();
 
  39         $xml->setIndent (true);
 
  40         $xml->startDocument('1.0','UTF-8');
 
  41             $xml->startElement ("OpenSearchDescription");
 
  42                 $xml->writeAttribute ("xmlns", "http://a9.com/-/spec/opensearch/1.1/");
 
  43                 $xml->startElement ("ShortName");
 
  44                     $xml->text ("My catalog");
 
  46                 $xml->startElement ("Description");
 
  47                     $xml->text ("Search for ebooks");
 
  49                 $xml->startElement ("InputEncoding");
 
  52                 $xml->startElement ("OutputEncoding");
 
  55                 $xml->startElement ("Image");
 
  56                     $xml->writeAttribute ("type", "image/x-icon");
 
  57                     $xml->writeAttribute ("width", "16");
 
  58                     $xml->writeAttribute ("height", "16");
 
  59                     $xml->text ($config['cops_icon']);
 
  61                 $xml->startElement ("Url");
 
  62                     $xml->writeAttribute ("type", 'application/atom+xml');
 
  63                     $urlparam = "?query={searchTerms}";
 
  64                     if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB));
 
  65                     $urlparam = str_replace ("%7B", "{", $urlparam);
 
  66                     $urlparam = str_replace ("%7D", "}", $urlparam);
 
  67                     $xml->writeAttribute ("template", $config['cops_full_url'] . 'feed.php' . $urlparam);
 
  69                 $xml->startElement ("Query");
 
  70                     $xml->writeAttribute ("role", "example");
 
  71                     $xml->writeAttribute ("searchTerms", "robot");
 
  75         return $xml->outputMemory(true);
 
  78     private function startXmlDocument ($page) {
 
  80         self::getXmlStream ()->startDocument('1.0','UTF-8');
 
  81         self::getXmlStream ()->startElement ("feed");
 
  82             self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom");
 
  83             self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
 
  84             self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog");
 
  85             self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/");
 
  86             self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/");
 
  87             self::getXmlStream ()->startElement ("title");
 
  88                 self::getXmlStream ()->text ($page->title);
 
  89             self::getXmlStream ()->endElement ();
 
  90             if ($page->subtitle != "")
 
  92                 self::getXmlStream ()->startElement ("subtitle");
 
  93                     self::getXmlStream ()->text ($page->subtitle);
 
  94                 self::getXmlStream ()->endElement ();
 
  96             self::getXmlStream ()->startElement ("id");
 
  99                     $idPage = $page->idPage;
 
 100                     if (!is_null (GetUrlParam (DB))) $idPage = str_replace ("cops:", "cops:" . GetUrlParam (DB) . ":", $idPage);
 
 101                     self::getXmlStream ()->text ($idPage);
 
 105                     self::getXmlStream ()->text ($_SERVER['REQUEST_URI']);
 
 107             self::getXmlStream ()->endElement ();
 
 108             self::getXmlStream ()->startElement ("updated");
 
 109                 self::getXmlStream ()->text (self::getUpdatedTime ());
 
 110             self::getXmlStream ()->endElement ();
 
 111             self::getXmlStream ()->startElement ("icon");
 
 112                 self::getXmlStream ()->text ($page->favicon);
 
 113             self::getXmlStream ()->endElement ();
 
 114             self::getXmlStream ()->startElement ("author");
 
 115                 self::getXmlStream ()->startElement ("name");
 
 116                     self::getXmlStream ()->text ($page->authorName);
 
 117                 self::getXmlStream ()->endElement ();
 
 118                 self::getXmlStream ()->startElement ("uri");
 
 119                     self::getXmlStream ()->text ($page->authorUri);
 
 120                 self::getXmlStream ()->endElement ();
 
 121                 self::getXmlStream ()->startElement ("email");
 
 122                     self::getXmlStream ()->text ($page->authorEmail);
 
 123                 self::getXmlStream ()->endElement ();
 
 124             self::getXmlStream ()->endElement ();
 
 125             $link = new LinkNavigation ("", "start", "Home");
 
 126             self::renderLink ($link);
 
 127             $link = new LinkNavigation ("?" . $_SERVER['QUERY_STRING'], "self");
 
 128             self::renderLink ($link);
 
 130             if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB));
 
 131             if ($config['cops_generate_invalid_opds_stream'] == 0 || preg_match("/(MantanoReader|FBReader)/", $_SERVER['HTTP_USER_AGENT'])) {
 
 132                 // Good and compliant way of handling search
 
 133                 $urlparam = addURLParameter ($urlparam, "page", self::PAGE_OPENSEARCH);
 
 134                 $link = new Link ("feed.php" . $urlparam, "application/opensearchdescription+xml", "search", "Search here");
 
 138                 // Bad way, will be removed when OPDS client are fixed
 
 139                 $urlparam = addURLParameter ($urlparam, "query", "{searchTerms}");
 
 140                 $urlparam = str_replace ("%7B", "{", $urlparam);
 
 141                 $urlparam = str_replace ("%7D", "}", $urlparam);
 
 142                 $link = new Link ($config['cops_full_url'] . 'feed.php' . $urlparam, "application/atom+xml", "search", "Search here");
 
 144             self::renderLink ($link);
 
 145             if ($page->containsBook () && !is_null ($config['cops_books_filter']) && count ($config['cops_books_filter']) > 0) {
 
 146                 $Urlfilter = getURLParam ("tag", "");
 
 147                 foreach ($config['cops_books_filter'] as $lib => $filter) {
 
 148                     $link = new LinkFacet ("?" . addURLParameter ($_SERVER['QUERY_STRING'], "tag", $filter), $lib, localize ("tagword.title"), $filter == $Urlfilter);
 
 149                     self::renderLink ($link);
 
 154     private function endXmlDocument () {
 
 155         self::getXmlStream ()->endElement ();
 
 156         self::getXmlStream ()->endDocument ();
 
 157         return self::getXmlStream ()->outputMemory(true);
 
 160     private function renderLink ($link) {
 
 161         self::getXmlStream ()->startElement ("link");
 
 162             self::getXmlStream ()->writeAttribute ("href", $link->href);
 
 163             self::getXmlStream ()->writeAttribute ("type", $link->type);
 
 164             if (!is_null ($link->rel)) {
 
 165                 self::getXmlStream ()->writeAttribute ("rel", $link->rel);
 
 167             if (!is_null ($link->title)) {
 
 168                 self::getXmlStream ()->writeAttribute ("title", $link->title);
 
 170             if (!is_null ($link->facetGroup)) {
 
 171                 self::getXmlStream ()->writeAttribute ("opds:facetGroup", $link->facetGroup);
 
 173             if ($link->activeFacet) {
 
 174                 self::getXmlStream ()->writeAttribute ("opds:activeFacet", "true");
 
 176         self::getXmlStream ()->endElement ();
 
 180     private function renderEntry ($entry) {
 
 181         self::getXmlStream ()->startElement ("title");
 
 182             self::getXmlStream ()->text ($entry->title);
 
 183         self::getXmlStream ()->endElement ();
 
 184         self::getXmlStream ()->startElement ("updated");
 
 185             self::getXmlStream ()->text (self::getUpdatedTime ());
 
 186         self::getXmlStream ()->endElement ();
 
 187         self::getXmlStream ()->startElement ("id");
 
 188             self::getXmlStream ()->text ($entry->id);
 
 189         self::getXmlStream ()->endElement ();
 
 190         self::getXmlStream ()->startElement ("content");
 
 191             self::getXmlStream ()->writeAttribute ("type", $entry->contentType);
 
 192             if ($entry->contentType == "text") {
 
 193                 self::getXmlStream ()->text ($entry->content);
 
 195                 self::getXmlStream ()->writeRaw ($entry->content);
 
 197         self::getXmlStream ()->endElement ();
 
 198         foreach ($entry->linkArray as $link) {
 
 199             self::renderLink ($link);
 
 202         if (get_class ($entry) != "EntryBook") {
 
 206         foreach ($entry->book->getAuthors () as $author) {
 
 207             self::getXmlStream ()->startElement ("author");
 
 208                 self::getXmlStream ()->startElement ("name");
 
 209                     self::getXmlStream ()->text ($author->name);
 
 210                 self::getXmlStream ()->endElement ();
 
 211                 self::getXmlStream ()->startElement ("uri");
 
 212                     self::getXmlStream ()->text ("feed.php" . $author->getUri ());
 
 213                 self::getXmlStream ()->endElement ();
 
 214             self::getXmlStream ()->endElement ();
 
 216         foreach ($entry->book->getTags () as $category) {
 
 217             self::getXmlStream ()->startElement ("category");
 
 218                 self::getXmlStream ()->writeAttribute ("term", $category->name);
 
 219                 self::getXmlStream ()->writeAttribute ("label", $category->name);
 
 220             self::getXmlStream ()->endElement ();
 
 222         if ($entry->book->getPubDate () != "") {
 
 223             self::getXmlStream ()->startElement ("dcterms:issued");
 
 224                 self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate));
 
 225             self::getXmlStream ()->endElement ();
 
 226             self::getXmlStream ()->startElement ("published");
 
 227                 self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate) . "T08:08:08Z");
 
 228             self::getXmlStream ()->endElement ();
 
 231         $lang = $entry->book->getLanguages ();
 
 232         if (!empty ($lang)) {
 
 233             self::getXmlStream ()->startElement ("dcterms:language");
 
 234                 self::getXmlStream ()->text ($lang);
 
 235             self::getXmlStream ()->endElement ();
 
 240     public function render ($page) {
 
 242         self::startXmlDocument ($page);
 
 243         if ($page->isPaginated ())
 
 245             self::getXmlStream ()->startElement ("opensearch:totalResults");
 
 246                 self::getXmlStream ()->text ($page->totalNumber);
 
 247             self::getXmlStream ()->endElement ();
 
 248             self::getXmlStream ()->startElement ("opensearch:itemsPerPage");
 
 249                 self::getXmlStream ()->text ($config['cops_max_item_per_page']);
 
 250             self::getXmlStream ()->endElement ();
 
 251             self::getXmlStream ()->startElement ("opensearch:startIndex");
 
 252                 self::getXmlStream ()->text (($page->n - 1) * $config['cops_max_item_per_page'] + 1);
 
 253             self::getXmlStream ()->endElement ();
 
 254             $prevLink = $page->getPrevLink ();
 
 255             $nextLink = $page->getNextLink ();
 
 256             if (!is_null ($prevLink)) {
 
 257                 self::renderLink ($prevLink);
 
 259             if (!is_null ($nextLink)) {
 
 260                 self::renderLink ($nextLink);
 
 263         foreach ($page->entryArray as $entry) {
 
 264             self::getXmlStream ()->startElement ("entry");
 
 265                 self::renderEntry ($entry);
 
 266             self::getXmlStream ()->endElement ();
 
 268         return self::endXmlDocument ();