|
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.