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 <[email protected]> |
7
|
|
|
*/ |
8
|
|
|
|
9
|
1 |
|
require_once dirname(__FILE__) . '/../base.php'; |
10
|
|
|
|
11
|
|
|
class OPDSRenderer |
|
|
|
|
12
|
|
|
{ |
13
|
|
|
private $xmlStream = NULL; |
14
|
|
|
private $updated = NULL; |
15
|
|
|
|
16
|
14 |
|
private function getUpdatedTime () { |
17
|
14 |
|
if (is_null ($this->updated)) { |
18
|
14 |
|
$this->updated = time(); |
19
|
14 |
|
} |
20
|
14 |
|
return date (DATE_ATOM, $this->updated); |
21
|
|
|
} |
22
|
|
|
|
23
|
14 |
|
private function getXmlStream () { |
24
|
14 |
|
if (is_null ($this->xmlStream)) { |
25
|
14 |
|
$this->xmlStream = new XMLWriter(); |
26
|
14 |
|
$this->xmlStream->openMemory(); |
27
|
14 |
|
$this->xmlStream->setIndent (true); |
28
|
14 |
|
} |
29
|
14 |
|
return $this->xmlStream; |
30
|
|
|
} |
31
|
|
|
|
32
|
1 |
|
public function getOpenSearch () { |
33
|
1 |
|
global $config; |
|
|
|
|
34
|
1 |
|
$xml = new XMLWriter (); |
35
|
1 |
|
$xml->openMemory (); |
36
|
1 |
|
$xml->setIndent (true); |
37
|
1 |
|
$xml->startDocument('1.0','UTF-8'); |
38
|
1 |
|
$xml->startElement ("OpenSearchDescription"); |
|
|
|
|
39
|
1 |
|
$xml->writeAttribute ("xmlns", "http://a9.com/-/spec/opensearch/1.1/"); |
|
|
|
|
40
|
1 |
|
$xml->startElement ("ShortName"); |
|
|
|
|
41
|
1 |
|
$xml->text ("My catalog"); |
|
|
|
|
42
|
1 |
|
$xml->endElement (); |
43
|
1 |
|
$xml->startElement ("Description"); |
|
|
|
|
44
|
1 |
|
$xml->text ("Search for ebooks"); |
|
|
|
|
45
|
1 |
|
$xml->endElement (); |
46
|
1 |
|
$xml->startElement ("InputEncoding"); |
|
|
|
|
47
|
1 |
|
$xml->text ("UTF-8"); |
|
|
|
|
48
|
1 |
|
$xml->endElement (); |
49
|
1 |
|
$xml->startElement ("OutputEncoding"); |
|
|
|
|
50
|
1 |
|
$xml->text ("UTF-8"); |
|
|
|
|
51
|
1 |
|
$xml->endElement (); |
52
|
1 |
|
$xml->startElement ("Image"); |
|
|
|
|
53
|
1 |
|
$xml->writeAttribute ("type", "image/x-icon"); |
|
|
|
|
54
|
1 |
|
$xml->writeAttribute ("width", "16"); |
|
|
|
|
55
|
1 |
|
$xml->writeAttribute ("height", "16"); |
|
|
|
|
56
|
1 |
|
$xml->text ($config['cops_icon']); |
57
|
1 |
|
$xml->endElement (); |
58
|
1 |
|
$xml->startElement ("Url"); |
|
|
|
|
59
|
1 |
|
$xml->writeAttribute ("type", 'application/atom+xml'); |
|
|
|
|
60
|
1 |
|
$urlparam = "?query={searchTerms}"; |
|
|
|
|
61
|
1 |
View Code Duplication |
if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB)); |
|
|
|
|
62
|
1 |
|
$urlparam = str_replace ("%7B", "{", $urlparam); |
|
|
|
|
63
|
1 |
|
$urlparam = str_replace ("%7D", "}", $urlparam); |
|
|
|
|
64
|
1 |
|
$xml->writeAttribute ("template", $config['cops_full_url'] . 'feed.php' . $urlparam); |
|
|
|
|
65
|
1 |
|
$xml->endElement (); |
66
|
1 |
|
$xml->startElement ("Query"); |
|
|
|
|
67
|
1 |
|
$xml->writeAttribute ("role", "example"); |
|
|
|
|
68
|
1 |
|
$xml->writeAttribute ("searchTerms", "robot"); |
|
|
|
|
69
|
1 |
|
$xml->endElement (); |
70
|
1 |
|
$xml->endElement (); |
71
|
1 |
|
$xml->endDocument(); |
72
|
1 |
|
return $xml->outputMemory(true); |
73
|
|
|
} |
74
|
|
|
|
75
|
14 |
|
private function startXmlDocument ($page) { |
76
|
14 |
|
global $config; |
|
|
|
|
77
|
14 |
|
self::getXmlStream ()->startDocument('1.0','UTF-8'); |
78
|
14 |
|
self::getXmlStream ()->startElement ("feed"); |
|
|
|
|
79
|
14 |
|
self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom"); |
|
|
|
|
80
|
14 |
|
self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml"); |
|
|
|
|
81
|
14 |
|
self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog"); |
|
|
|
|
82
|
14 |
|
self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/"); |
|
|
|
|
83
|
14 |
|
self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/"); |
|
|
|
|
84
|
14 |
|
self::getXmlStream ()->startElement ("title"); |
|
|
|
|
85
|
14 |
|
self::getXmlStream ()->text ($page->title); |
86
|
14 |
|
self::getXmlStream ()->endElement (); |
87
|
14 |
|
if ($page->subtitle != "") |
|
|
|
|
88
|
14 |
|
{ |
89
|
1 |
|
self::getXmlStream ()->startElement ("subtitle"); |
|
|
|
|
90
|
1 |
|
self::getXmlStream ()->text ($page->subtitle); |
91
|
1 |
|
self::getXmlStream ()->endElement (); |
92
|
1 |
|
} |
93
|
14 |
|
self::getXmlStream ()->startElement ("id"); |
|
|
|
|
94
|
14 |
|
if ($page->idPage) |
95
|
14 |
|
{ |
96
|
13 |
|
$idPage = $page->idPage; |
97
|
13 |
View Code Duplication |
if (!is_null (GetUrlParam (DB))) $idPage = str_replace ("cops:", "cops:" . GetUrlParam (DB) . ":", $idPage); |
|
|
|
|
98
|
13 |
|
self::getXmlStream ()->text ($idPage); |
99
|
13 |
|
} |
100
|
|
|
else |
101
|
|
|
{ |
102
|
1 |
|
self::getXmlStream ()->text ($_SERVER['REQUEST_URI']); |
103
|
|
|
} |
104
|
14 |
|
self::getXmlStream ()->endElement (); |
105
|
14 |
|
self::getXmlStream ()->startElement ("updated"); |
|
|
|
|
106
|
14 |
|
self::getXmlStream ()->text (self::getUpdatedTime ()); |
107
|
14 |
|
self::getXmlStream ()->endElement (); |
108
|
14 |
|
self::getXmlStream ()->startElement ("icon"); |
|
|
|
|
109
|
14 |
|
self::getXmlStream ()->text ($page->favicon); |
110
|
14 |
|
self::getXmlStream ()->endElement (); |
111
|
14 |
|
self::getXmlStream ()->startElement ("author"); |
|
|
|
|
112
|
14 |
|
self::getXmlStream ()->startElement ("name"); |
|
|
|
|
113
|
14 |
|
self::getXmlStream ()->text ($page->authorName); |
114
|
14 |
|
self::getXmlStream ()->endElement (); |
115
|
14 |
|
self::getXmlStream ()->startElement ("uri"); |
|
|
|
|
116
|
14 |
|
self::getXmlStream ()->text ($page->authorUri); |
117
|
14 |
|
self::getXmlStream ()->endElement (); |
118
|
14 |
|
self::getXmlStream ()->startElement ("email"); |
|
|
|
|
119
|
14 |
|
self::getXmlStream ()->text ($page->authorEmail); |
120
|
14 |
|
self::getXmlStream ()->endElement (); |
121
|
14 |
|
self::getXmlStream ()->endElement (); |
122
|
14 |
|
$link = new LinkNavigation ("", "start", "Home"); |
|
|
|
|
123
|
14 |
|
self::renderLink ($link); |
124
|
14 |
|
$link = new LinkNavigation ("?" . getQueryString (), "self"); |
|
|
|
|
125
|
14 |
|
self::renderLink ($link); |
126
|
14 |
|
$urlparam = "?"; |
|
|
|
|
127
|
14 |
View Code Duplication |
if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB)); |
|
|
|
|
128
|
14 |
|
if ($config['cops_generate_invalid_opds_stream'] == 0 || preg_match("/(MantanoReader|FBReader)/", $_SERVER['HTTP_USER_AGENT'])) { |
|
|
|
|
129
|
|
|
// Good and compliant way of handling search |
130
|
14 |
|
$urlparam = addURLParameter ($urlparam, "page", Base::PAGE_OPENSEARCH); |
|
|
|
|
131
|
14 |
|
$link = new Link ("feed.php" . $urlparam, "application/opensearchdescription+xml", "search", "Search here"); |
|
|
|
|
132
|
14 |
|
} |
133
|
|
|
else |
134
|
|
|
{ |
135
|
|
|
// Bad way, will be removed when OPDS client are fixed |
136
|
1 |
|
$urlparam = addURLParameter ($urlparam, "query", "{searchTerms}"); |
|
|
|
|
137
|
1 |
|
$urlparam = str_replace ("%7B", "{", $urlparam); |
|
|
|
|
138
|
1 |
|
$urlparam = str_replace ("%7D", "}", $urlparam); |
|
|
|
|
139
|
1 |
|
$link = new Link ($config['cops_full_url'] . 'feed.php' . $urlparam, "application/atom+xml", "search", "Search here"); |
|
|
|
|
140
|
|
|
} |
141
|
14 |
|
self::renderLink ($link); |
142
|
14 |
|
if ($page->containsBook () && !is_null ($config['cops_books_filter']) && count ($config['cops_books_filter']) > 0) { |
143
|
1 |
|
$Urlfilter = getURLParam ("tag", ""); |
|
|
|
|
144
|
1 |
|
foreach ($config['cops_books_filter'] as $lib => $filter) { |
145
|
1 |
|
$link = new LinkFacet ("?" . addURLParameter (getQueryString (), "tag", $filter), $lib, localize ("tagword.title"), $filter == $Urlfilter); |
|
|
|
|
146
|
1 |
|
self::renderLink ($link); |
147
|
1 |
|
} |
148
|
1 |
|
} |
149
|
14 |
|
} |
150
|
|
|
|
151
|
14 |
|
private function endXmlDocument () { |
152
|
14 |
|
self::getXmlStream ()->endElement (); |
153
|
14 |
|
self::getXmlStream ()->endDocument (); |
154
|
14 |
|
return self::getXmlStream ()->outputMemory(true); |
155
|
|
|
} |
156
|
|
|
|
157
|
14 |
|
private function renderLink ($link) { |
158
|
14 |
|
self::getXmlStream ()->startElement ("link"); |
|
|
|
|
159
|
14 |
|
self::getXmlStream ()->writeAttribute ("href", $link->href); |
|
|
|
|
160
|
14 |
|
self::getXmlStream ()->writeAttribute ("type", $link->type); |
|
|
|
|
161
|
14 |
|
if (!is_null ($link->rel)) { |
162
|
14 |
|
self::getXmlStream ()->writeAttribute ("rel", $link->rel); |
|
|
|
|
163
|
14 |
|
} |
164
|
14 |
|
if (!is_null ($link->title)) { |
165
|
14 |
|
self::getXmlStream ()->writeAttribute ("title", $link->title); |
|
|
|
|
166
|
14 |
|
} |
167
|
14 |
|
if (!is_null ($link->facetGroup)) { |
168
|
1 |
|
self::getXmlStream ()->writeAttribute ("opds:facetGroup", $link->facetGroup); |
|
|
|
|
169
|
1 |
|
} |
170
|
14 |
|
if ($link->activeFacet) { |
171
|
1 |
|
self::getXmlStream ()->writeAttribute ("opds:activeFacet", "true"); |
|
|
|
|
172
|
1 |
|
} |
173
|
14 |
|
self::getXmlStream ()->endElement (); |
174
|
14 |
|
} |
175
|
|
|
|
176
|
5 |
|
private function getPublicationDate($book) { |
177
|
5 |
|
$dateYmd = substr($book->pubdate, 0, 10); |
178
|
5 |
|
$pubdate = \DateTime::createFromFormat('Y-m-d', $dateYmd); |
179
|
5 |
|
if ($pubdate === false || |
180
|
5 |
|
$pubdate->format ("Y") == "0101" || |
|
|
|
|
181
|
5 |
|
$pubdate->format ("Y") == "0100") { |
|
|
|
|
182
|
|
|
return ""; |
|
|
|
|
183
|
|
|
} |
184
|
5 |
|
return $pubdate->format("Y-m-d"); |
|
|
|
|
185
|
|
|
} |
186
|
|
|
|
187
|
14 |
|
private function renderEntry ($entry) { |
188
|
14 |
|
self::getXmlStream ()->startElement ("title"); |
|
|
|
|
189
|
14 |
|
self::getXmlStream ()->text ($entry->title); |
190
|
14 |
|
self::getXmlStream ()->endElement (); |
191
|
14 |
|
self::getXmlStream ()->startElement ("updated"); |
|
|
|
|
192
|
14 |
|
self::getXmlStream ()->text (self::getUpdatedTime ()); |
193
|
14 |
|
self::getXmlStream ()->endElement (); |
194
|
14 |
|
self::getXmlStream ()->startElement ("id"); |
|
|
|
|
195
|
14 |
|
self::getXmlStream ()->text ($entry->id); |
196
|
14 |
|
self::getXmlStream ()->endElement (); |
197
|
14 |
|
self::getXmlStream ()->startElement ("content"); |
|
|
|
|
198
|
14 |
|
self::getXmlStream ()->writeAttribute ("type", $entry->contentType); |
|
|
|
|
199
|
14 |
|
if ($entry->contentType == "text") { |
|
|
|
|
200
|
9 |
|
self::getXmlStream ()->text ($entry->content); |
201
|
9 |
|
} else { |
202
|
5 |
|
self::getXmlStream ()->writeRaw ($entry->content); |
203
|
|
|
} |
204
|
14 |
|
self::getXmlStream ()->endElement (); |
205
|
14 |
|
foreach ($entry->linkArray as $link) { |
206
|
14 |
|
self::renderLink ($link); |
207
|
14 |
|
} |
208
|
|
|
|
209
|
14 |
|
if (get_class ($entry) != "EntryBook") { |
|
|
|
|
210
|
9 |
|
return; |
211
|
|
|
} |
212
|
|
|
|
213
|
5 |
|
foreach ($entry->book->getAuthors () as $author) { |
214
|
5 |
|
self::getXmlStream ()->startElement ("author"); |
|
|
|
|
215
|
5 |
|
self::getXmlStream ()->startElement ("name"); |
|
|
|
|
216
|
5 |
|
self::getXmlStream ()->text ($author->name); |
217
|
5 |
|
self::getXmlStream ()->endElement (); |
218
|
5 |
|
self::getXmlStream ()->startElement ("uri"); |
|
|
|
|
219
|
5 |
|
self::getXmlStream ()->text ("feed.php" . $author->getUri ()); |
|
|
|
|
220
|
5 |
|
self::getXmlStream ()->endElement (); |
221
|
5 |
|
self::getXmlStream ()->endElement (); |
222
|
5 |
|
} |
223
|
5 |
|
foreach ($entry->book->getTags () as $category) { |
224
|
5 |
|
self::getXmlStream ()->startElement ("category"); |
|
|
|
|
225
|
5 |
|
self::getXmlStream ()->writeAttribute ("term", $category->name); |
|
|
|
|
226
|
5 |
|
self::getXmlStream ()->writeAttribute ("label", $category->name); |
|
|
|
|
227
|
5 |
|
self::getXmlStream ()->endElement (); |
228
|
5 |
|
} |
229
|
5 |
|
if ($entry->book->getPubDate () != "") { |
|
|
|
|
230
|
5 |
|
self::getXmlStream ()->startElement ("dcterms:issued"); |
|
|
|
|
231
|
5 |
|
self::getXmlStream ()->text (self::getPublicationDate($entry->book)); |
232
|
5 |
|
self::getXmlStream ()->endElement (); |
233
|
5 |
|
self::getXmlStream ()->startElement ("published"); |
|
|
|
|
234
|
5 |
|
self::getXmlStream ()->text (self::getPublicationDate($entry->book) . "T08:08:08Z"); |
|
|
|
|
235
|
5 |
|
self::getXmlStream ()->endElement (); |
236
|
5 |
|
} |
237
|
|
|
|
238
|
5 |
|
$lang = $entry->book->getLanguages (); |
239
|
5 |
|
if (!empty ($lang)) { |
240
|
5 |
|
self::getXmlStream ()->startElement ("dcterms:language"); |
|
|
|
|
241
|
5 |
|
self::getXmlStream ()->text ($lang); |
242
|
5 |
|
self::getXmlStream ()->endElement (); |
243
|
5 |
|
} |
244
|
|
|
|
245
|
5 |
|
} |
246
|
|
|
|
247
|
14 |
|
public function render ($page) { |
248
|
14 |
|
global $config; |
|
|
|
|
249
|
14 |
|
self::startXmlDocument ($page); |
250
|
14 |
|
if ($page->isPaginated ()) |
251
|
14 |
|
{ |
252
|
1 |
|
self::getXmlStream ()->startElement ("opensearch:totalResults"); |
|
|
|
|
253
|
1 |
|
self::getXmlStream ()->text ($page->totalNumber); |
254
|
1 |
|
self::getXmlStream ()->endElement (); |
255
|
1 |
|
self::getXmlStream ()->startElement ("opensearch:itemsPerPage"); |
|
|
|
|
256
|
1 |
|
self::getXmlStream ()->text ($config['cops_max_item_per_page']); |
257
|
1 |
|
self::getXmlStream ()->endElement (); |
258
|
1 |
|
self::getXmlStream ()->startElement ("opensearch:startIndex"); |
|
|
|
|
259
|
1 |
|
self::getXmlStream ()->text (($page->n - 1) * $config['cops_max_item_per_page'] + 1); |
260
|
1 |
|
self::getXmlStream ()->endElement (); |
261
|
1 |
|
$prevLink = $page->getPrevLink (); |
262
|
1 |
|
$nextLink = $page->getNextLink (); |
263
|
1 |
|
if (!is_null ($prevLink)) { |
264
|
1 |
|
self::renderLink ($prevLink); |
265
|
1 |
|
} |
266
|
1 |
|
if (!is_null ($nextLink)) { |
267
|
1 |
|
self::renderLink ($nextLink); |
268
|
1 |
|
} |
269
|
1 |
|
} |
270
|
14 |
|
foreach ($page->entryArray as $entry) { |
271
|
14 |
|
self::getXmlStream ()->startElement ("entry"); |
|
|
|
|
272
|
14 |
|
self::renderEntry ($entry); |
273
|
14 |
|
self::getXmlStream ()->endElement (); |
274
|
14 |
|
} |
275
|
14 |
|
return self::endXmlDocument (); |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.