These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 Klaus Broelemann <[email protected]> |
||
7 | */ |
||
8 | |||
9 | require_once('base.php'); |
||
10 | |||
11 | /** |
||
12 | * Read and filter virtual libraries |
||
13 | */ |
||
14 | class VirtualLib { |
||
15 | const SQL_VL_KEY = "virtual_libraries"; // Key for the virtual library entries. |
||
16 | |||
17 | private $db_id = null; // Database Index for the virtual lib |
||
18 | private $vl_id = null; // Library Index |
||
19 | private $filter = null; // structured representation of the current filter |
||
20 | |||
21 | private static $currentVL = null; // Singleton: current virtual lib |
||
22 | |||
23 | /** |
||
24 | * The constructor parses the calibre search string and creates a array-based representation out of it. |
||
25 | * |
||
26 | * @param int $database The database id of the new object. |
||
27 | * @param int $virtualLib The virtual library id of the new object. |
||
28 | */ |
||
29 | private function __construct($database, $virtualLib) { |
||
30 | $this->db_id = $database; |
||
31 | $this->vl_id = $virtualLib; |
||
32 | |||
33 | // Get the current search string |
||
34 | $vlList = self::getVLList($database); |
||
35 | $vlList = array_values($vlList); |
||
36 | $searchStr = $vlList[$virtualLib]; |
||
37 | |||
38 | $this->filter = Filter::parseFilter($searchStr); |
||
39 | } |
||
40 | |||
41 | /** |
||
42 | * Returns a SQL query that finds the IDs of all books accepted by the filter. |
||
43 | * |
||
44 | * The sql statement return only one column with the name 'id'. |
||
45 | * This statement can be included into other sql statements in order to apply the filter, e.g. by using inner joins |
||
46 | * like "select books.* from books inner join ({0}) as filter on books.id = filter.id" |
||
47 | * @see Filter |
||
48 | * |
||
49 | * @return string an sql query |
||
50 | */ |
||
51 | public function getFilterQuery() { |
||
52 | return $this->filter->toSQLQuery(); |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * Get the name of the virtual library. |
||
57 | * |
||
58 | * @return string Name of the virtual library. |
||
59 | */ |
||
60 | public function getName() { |
||
61 | $names = self::getVLNameList($this->db_id); |
||
62 | return $names[$this->vl_id]; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Get the current VirtualLib object. |
||
67 | * |
||
68 | * @param int $database The current database id. |
||
69 | * @param int $virtualLib The current virtual library id. |
||
70 | * @return VirtualLib The corresponding VirtualLib object. |
||
71 | */ |
||
72 | public static function getVL($database = null, $virtualLib = null) { |
||
73 | if (is_null($database)) |
||
74 | $database = getURLParam(DB, 0); |
||
75 | if ( is_null(self::$currentVL) || self::$currentVL->db_id != $database || (self::$currentVL->vl_id != $virtualLib && !is_null($virtualLib))) { |
||
76 | if (is_null($virtualLib)) |
||
77 | $virtualLib = GetUrlParam (VL, 0); |
||
78 | self::$currentVL = new VirtualLib($database, $virtualLib); |
||
79 | } |
||
80 | return self::$currentVL; |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * Checks if the support for virtual libraries is enabled in the settings. |
||
85 | * |
||
86 | * @return boolean true, if virtual libraries are enabled. |
||
87 | */ |
||
88 | public static function isVLEnabled() { |
||
89 | global $config; |
||
90 | return ($config['enable_virtual_libraries'] == 1); |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * Gets a list of all virtual libraries in a database. |
||
95 | * If virtual libraries are disabled, only an empty entry is returned. |
||
96 | * |
||
97 | * @param int $database id of the database |
||
98 | * @return array An array of virtual libraries with the names as keys and the filter strings as values. |
||
99 | */ |
||
100 | public static function getVLList($database = NULL) { |
||
101 | // Standard return if virtual libraries are not enabled |
||
102 | if (!self::isVLEnabled()) |
||
103 | return array("" => ""); |
||
104 | // Load list from Database |
||
105 | $vLibs = json_decode(Base::getCalibreSetting(self::SQL_VL_KEY, $database), true); |
||
106 | // Add "All Books" at the beginning |
||
107 | if (is_null($vLibs)) |
||
108 | return array(localize ("allbooks.title") => ""); |
||
109 | else |
||
110 | return array_merge(array(localize ("allbooks.title") => ""), $vLibs); |
||
111 | } |
||
112 | 41 | ||
113 | 41 | /** |
|
114 | 41 | * Gets a list of all virtual libraries in a database. |
|
115 | * |
||
116 | * @param int $database id of the database |
||
117 | * @return array An array of virtual libraries with the names as keys and the filter strings as values. |
||
118 | */ |
||
119 | public static function getVLNameList($database = NULL) { |
||
120 | return array_keys(self::getVLList($database)); |
||
121 | } |
||
122 | |||
123 | 22 | /** |
|
124 | * Combines the database and virtual lib names into a merged name. |
||
125 | 22 | * |
|
126 | * The resulting name has the form "{$dbName} - {$vlName}". If one of these parameters is empty, the dash will also be removed. |
||
127 | 22 | * If the support for virtual libraries is not enabled, this function simply returns the database name. |
|
128 | 22 | * |
|
129 | * @param string $dbName The database name. If NULL, the name of the current db is taken. |
||
130 | * @param string $vlName The name of the virtual library. If NULL, the name of the current vl is taken. |
||
131 | */ |
||
132 | public static function getDisplayName($dbName = NULL, $vlName = NULL) { |
||
133 | if (is_null($dbName)) |
||
134 | $dbName = Base::getDbName(); |
||
135 | if (is_null($vlName)) |
||
136 | $vlName = self::getVL()->getName(); |
||
137 | if (self::isVLEnabled()) |
||
138 | return trim(str_format('{0} - {1}', $dbName, $vlName), ' -'); |
||
139 | 22 | else |
|
140 | 22 | return $dbName; |
|
141 | } |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * Abstract classe to store filters internally. It's derived classes represent the different filter types. |
||
146 | * |
||
147 | */ |
||
148 | abstract class Filter { |
||
149 | // Special settings for known attributes |
||
150 | private static $KNOWN_ATTRIBUTES = array( |
||
151 | "authors" => array(), |
||
152 | 1 | "series" => array("link_join_on" => "series"), |
|
153 | 1 | "publishers" => array(), |
|
154 | 1 | "tags" => array(), |
|
155 | "ratings" => array("filterColumn" => "rating"), |
||
156 | 1 | "languages" => array("filterColumn" => "lang_code", "link_join_on" => "lang_code"), |
|
157 | "formats" => array("table" => "data", "filterColumn" => "format", "link_table" => "data", "link_join_on" => "id"), |
||
158 | ); |
||
159 | |||
160 | private $isNegated = false; |
||
161 | |||
162 | /** |
||
163 | * Creates the attribute settings |
||
164 | * @param string $attr the name of the attribute, e.g. "tags" |
||
165 | * @return array an assotiative array with the keys "table", "filterColumn", "link_table", "link_join_on", "bookID". |
||
166 | */ |
||
167 | public static function getAttributeSettings($attr) { |
||
168 | $attr = self::normalizeAttribute($attr); |
||
169 | if (!array_key_exists($attr, self::$KNOWN_ATTRIBUTES)) |
||
170 | return null; |
||
171 | return self::$KNOWN_ATTRIBUTES[$attr] + array( |
||
172 | "table" => $attr, |
||
173 | "filterColumn" => "name", |
||
174 | "link_table" => "books_" . $attr . "_link", |
||
175 | "link_join_on" => substr($attr, 0, strlen($attr) - 1), |
||
176 | "bookID" => "book" |
||
177 | ); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Normalizes the attribute. |
||
182 | * |
||
183 | * Some attributes can be used in plural (e.g. languages) and singular (e.g. language). This function appends a missing s and puts everything to lower case |
||
184 | * @param string $attr the attribute, like it was used in calibre |
||
185 | * @return the normalized attribute name |
||
186 | */ |
||
187 | public static function normalizeAttribute($attr) { |
||
188 | $attr = strtolower($attr); |
||
189 | if (substr($attr, -1) != 's') |
||
190 | $attr .= 's'; |
||
191 | return $attr; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Gets the from - part of a table, its link-table and a placeholder for the filter |
||
196 | * |
||
197 | * @param string $table a table, e.g. "authors" |
||
198 | * @return string a from string with a placeholder for the filter query |
||
199 | */ |
||
200 | public static function getLinkedTable($table) { |
||
201 | foreach (array_keys(self::$KNOWN_ATTRIBUTES) as $attr) { |
||
202 | $tabInfo = self::getAttributeSettings($attr); |
||
203 | if ($tabInfo["table"] == $table) { |
||
204 | return str_format_n( |
||
205 | "{table} inner join {link_table} as link on {table}.id = link.{link_join_on} |
||
206 | inner join ({placeholder}) as filter on filter.id = link.{bookID}", $tabInfo + array("placeholder" => "{0}")); |
||
207 | } |
||
208 | } |
||
209 | return $table; |
||
210 | } |
||
211 | /** |
||
212 | * Converts the calibre search string into afilter object |
||
213 | * |
||
214 | * @param string $searchStr The calibre string |
||
215 | * @return Filter The internal, array-based representation |
||
216 | */ |
||
217 | public static function parseFilter($searchStr) { |
||
218 | // deal with empty input strings |
||
219 | if (strlen($searchStr) == 0) |
||
220 | return new EmptyFilter(); |
||
221 | |||
222 | // Simple search string pattern. It recognizes search string of the form |
||
223 | // [attr]:[value] |
||
224 | // and their negation |
||
225 | // not [attr]:[value] |
||
226 | // where value is either a number, a boolean or a string in double quote. |
||
227 | // In the latter case, the string starts with an operator (= or ~), followed by the search text. |
||
228 | // TODO: deal with more complex search terms that can contain "and", "or" and brackets |
||
229 | $pattern = '#(?P<neg>not)?\s*(?P<attr>\w+):(?P<value>"(?P<op>=|~|\>|<|>=|<=)(?P<text>.*)"|true|false|\d+)#i'; |
||
230 | if (!preg_match($pattern, $searchStr, $match)) { |
||
231 | trigger_error("Virtual Library Filter is not supported.", E_USER_WARNING); |
||
232 | return new EmptyFilter(); |
||
233 | } |
||
234 | |||
235 | // Create the actual filter object |
||
236 | $value = $match["value"]; |
||
237 | $filter = null; |
||
0 ignored issues
–
show
|
|||
238 | if (substr($value, 0, 1) == '"') { |
||
239 | $filter = new ComparingFilter($match["attr"], $match["text"], $match["op"]); |
||
240 | } elseif (preg_match("#\d+#", $value)) { |
||
241 | $filter = new ComparingFilter($match["attr"], $value); |
||
242 | } else { |
||
243 | $value = (strcasecmp($value, "true") == 0); |
||
244 | $filter = new ExistenceFilter($match["attr"], $value); |
||
245 | } |
||
246 | |||
247 | // Negate if a leading "not" is given |
||
248 | if (strlen($match["neg"]) > 0) |
||
249 | $filter->negate(); |
||
250 | |||
251 | return $filter; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Returns a SQL query that finds the IDs of all books accepted by the filter. The single columns name is id. |
||
256 | */ |
||
257 | public abstract function toSQLQuery(); |
||
258 | |||
259 | /** |
||
260 | * Negates the current filter. A second call will undo it. |
||
261 | */ |
||
262 | public function negate() { |
||
263 | $this->isNegated = !$this->isNegated; |
||
264 | } |
||
265 | |||
266 | public function isNegated() { |
||
267 | return $this->isNegated; |
||
268 | } |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Class that represents an empty filter |
||
273 | * |
||
274 | */ |
||
275 | class EmptyFilter extends Filter { |
||
276 | public function __construct() { |
||
277 | // Do Nothing |
||
278 | } |
||
279 | |||
280 | // Return all books (or no book if the filter is negated) |
||
281 | public function toSQLQuery() { |
||
282 | if ($this->isNegated()) |
||
283 | return "select id from books where 1 = 0"; |
||
284 | return "select id from books"; |
||
285 | } |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Class that represents a filter, that compares an attribute with a given value, e.g. tags with "Fiction" |
||
290 | * |
||
291 | * This class allows for other comparation operators beside "=" |
||
292 | */ |
||
293 | class ComparingFilter extends Filter { |
||
294 | private $attr = null; // The attribute that is filtered |
||
295 | private $value = null; // The value with which to compare |
||
296 | private $op = null; // The operator that is used for comparing |
||
297 | |||
298 | /** |
||
299 | * Creates a comparing filter |
||
300 | * |
||
301 | * @param string $attr The attribute that is filtered. |
||
302 | * @param mixed $value The value with which to compare. |
||
303 | * @param string $op The operator that is used for comparing, optional. |
||
304 | */ |
||
305 | public function __construct($attr, $value, $op = "=") { |
||
306 | $this->attr = self::normalizeAttribute($attr); |
||
307 | $this->value = $value; |
||
308 | if ($op == "~") |
||
309 | $op = "like"; |
||
310 | $this->op = $op; |
||
311 | |||
312 | // Specialty of ratings |
||
313 | if ($this->attr == "ratings") |
||
314 | $this->value *= 2; |
||
315 | |||
316 | // Specialty of languages |
||
317 | // This only works if calibre and cops use the same language!!! |
||
318 | if ($this->attr == "languages") |
||
319 | $this->value = Language::getLanguageCode($this->value); |
||
320 | } |
||
321 | |||
322 | public function toSQLQuery() { |
||
323 | $queryParams = self::getAttributeSettings($this->attr); |
||
324 | // Do not filter if attribute is not valid |
||
325 | if (is_null($queryParams)) |
||
326 | return "select id from books"; |
||
327 | |||
328 | // Include parameters into the sql query |
||
329 | $queryParams["value"] = $this->value; |
||
330 | $queryParams["op"] = $this->op; |
||
331 | $queryParams["neg"] = $this->isNegated() ? "not" : ""; |
||
332 | if ($this->attr == "formats") |
||
333 | $sql = str_format_n( |
||
334 | "select distinct {table}.{bookID} as id ". |
||
335 | "from {table} ". |
||
336 | "where {neg} ({table}.{filterColumn} {op} '{value}')", |
||
337 | $queryParams); |
||
338 | else |
||
339 | $sql = str_format_n( |
||
340 | "select distinct {link_table}.{bookID} as id ". |
||
341 | "from {table} inner join {link_table} on {table}.id = {link_table}.{link_join_on} ". |
||
342 | "where {neg} ({table}.{filterColumn} {op} '{value}')", |
||
343 | $queryParams); |
||
344 | return $sql; |
||
345 | } |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * Class that represents a filter, that checks if a given attribute exists for a book. |
||
350 | */ |
||
351 | class ExistenceFilter extends Filter { |
||
352 | private $attr = null; // The attribute that is filtered |
||
353 | |||
354 | /** |
||
355 | * Creates an existence filter |
||
356 | * |
||
357 | * @param string $attr The attribute that is filtered. |
||
358 | * @param boolean $value True, if objects with that attribute are accepted by the filter, false if not. |
||
359 | */ |
||
360 | public function __construct($attr, $value = true) { |
||
361 | $this->attr = $attr; |
||
362 | |||
363 | // $value == false is the negation of $value == true |
||
364 | if (!$value) |
||
365 | $this->negate(); |
||
366 | } |
||
367 | |||
368 | public function toSQLQuery() { |
||
369 | $queryParams = self::getAttributeSettings($this->attr); |
||
370 | // Do not filter if attribute is not valid |
||
371 | if (is_null($queryParams)) |
||
372 | return "select id from books"; |
||
373 | |||
374 | // Include parameters into the sql query |
||
375 | $queryParams["op"] = $this->isNegated() ? "==" : ">"; |
||
376 | $sql = str_format_n( |
||
377 | "select books.id as id from books left join {link_table} as link on link.{bookID} = books.id group by books.id having count(link.{link_join_on}) {op} 0", |
||
378 | $queryParams); |
||
379 | return $sql; |
||
380 | } |
||
381 | } |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.