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 ();