Completed
Pull Request — master (#233)
by
unknown
08:50
created

virtuallib.php (1 issue)

Upgrade to new PHP Analysis Engine

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
	public static $KNOWN_ATTRIBUTES = array(
150
		"tags" => array(
151
			"table"        => "tags",
152 1
			"filterColumn" => "name",
153 1
			"link_table"   => "books_tags_link",
154 1
			"link_join_on" => "tag",
155
			"bookID"       => "book"
156 1
		)
157
	);
158
	
159
	
160
	private $isNegated = false;
161
	
162
	/**
163
	 * Converts the calibre search string into afilter object
164
	 *
165
	 * @param string $searchStr The calibre string
166
	 * @return Filter The internal, array-based representation
167
	 */
168
	public static function parseFilter($searchStr) {
169
		// deal with empty input strings
170
		if (strlen($searchStr) == 0)
171
			return new EmptyFilter();
172
	
173
		// Simple search string pattern. It recognizes search string of the form
174
		//     [attr]:[value]
175
		// and their negation
176
		//     not [attr]:[value]
177
		// where value is either a number, a boolean or a string in double quote.
178
		// In the latter case, the string starts with an operator (= or ~), followed by the search text.
179
		// TODO: deal with more complex search terms that can contain "and", "or" and brackets
180
		$pattern = '#(?P<neg>not)?\s*(?P<attr>\w+):(?P<value>"(?P<op>=|~)(?P<text>.*)"|true|false|\d+)#i';
181
		if (!preg_match($pattern, $searchStr, $match)) {
182
			trigger_error("Virtual Library Filter is not supported.", E_USER_WARNING);
183
			return new EmptyFilter();
184
		}
185
	
186
		// Create the actual filter object
187
		$value = $match["value"];
188
		$filter   = null;
0 ignored issues
show
$filter is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

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.

Loading history...
189
		if (substr($value, 0, 1) == '"') {
190
			$filter = new ComparingFilter($match["attr"], $match["text"], $match["op"]);
191
		} elseif (preg_match("#\d+", $value)) {
192
			$filter = new ComparingFilter($match["attr"], $value, $match["op"]);
193
		} else {
194
			$value = (strcasecmp($value, "true") == 0);
195
			$filter = new ExistenceFilter($match["attr"], $value);
196
		}
197
	
198
		// Negate if a leading "not" is given
199
		if (strlen($match["neg"]) > 0)
200
			$filter->negate();
201
	
202
		return $filter;
203
	}
204
	
205
	/**
206
	 * Returns a SQL query that finds the IDs of all books accepted by the filter. The single columns name is id.
207
	 */
208
	public abstract function toSQLQuery();
209
	
210
	/**
211
	 * Negates the current filter. A second call will undo it.
212
	 */
213
	public function negate() {
214
		$this->isNegated = !$this->isNegated;
215
	}
216
	
217
	public function isNegated() {
218
		return $this->isNegated;
219
	}
220
}
221
222
/**
223
 * Class that represents an empty filter
224
 *
225
 */
226
class EmptyFilter extends Filter {
227
	public function __construct() {
228
		// Do Nothing
229
	}
230
	
231
	// Return all books (or no book if the filter is negated)
232
	public function toSQLQuery() {
233
		if ($this->isNegated())
234
			return "select id from books where 1 = 0";
235
		return "select id from books";
236
	}
237
}
238
239
/**
240
 * Class that represents a filter, that compares an attribute with a given value, e.g. tags with "Fiction" 
241
 *
242
 * This class allows for other comparation operators beside "="
243
 */
244
class ComparingFilter extends Filter {
245
	private $attr = null;   // The attribute that is filtered
246
	private $value = null;  // The value with which to compare
247
	private $op = null;     // The operator that is used for comparing
248
	
249
	/**
250
	 * Creates a comparing filter
251
	 * 
252
	 * @param string $attr The attribute that is filtered.
253
	 * @param mixed $value The value with which to compare.
254
	 * @param string $op The operator that is used for comparing, optional.
255
	 */
256
	public function __construct($attr, $value, $op = "=") {
257
		$this->attr = strtolower($attr);
258
		$this->value = $value;
259
		$this->op = $op;
260
	}
261
	
262
	public function toSQLQuery() {
263
		// Do not filter if attribute is not valid
264
		if (!array_key_exists($this->attr, self::$KNOWN_ATTRIBUTES))
265
			return "select id from books";
266
		
267
		// Include parameters into the sql query
268
		$queryParams = self::$KNOWN_ATTRIBUTES[$this->attr];
269
		$queryParams["value"] = $this->value;
270
		$sql = str_format_n(
271
				"select distinct {link_table}.{bookID} as id ".
272
				"from {table} inner join {link_table} on {table}.id = {link_table}.{link_join_on} ".
273
				"where {table}.{filterColumn} = '{value}'",
274
				$queryParams);
275
		// TODO: support different operators
276
		return $sql;
277
	}
278
}
279
280
/**
281
 * Class that represents a filter, that checks if a given attribute exists for a book.
282
 */
283
class ExistenceFilter extends Filter {
284
	private $attr = null;   // The attribute that is filtered
285
286
	/**
287
	 * Creates an existence filter
288
	 *
289
	 * @param string $attr The attribute that is filtered.
290
	 * @param boolean $value True, if objects with that attribute are accepted by the filter, false if not.
291
	 */
292
	public function __construct($attr, $value = true) {
293
		$this->attr = $attr;
294
		
295
		// $value == false is the negation of $value == true 
296
		if (!$value)
297
			$this->negate();
298
	}
299
	
300
	public function toSQLQuery() {
301
		// Do not filter if attribute is not valid
302
		if (!array_key_exists($this->attr, self::KNOWN_ATTRIBUTES))
303
			return "select id from books";
304
	
305
		// Include parameters into the sql query
306
		$queryParams = self::$KNOWN_ATTRIBUTES[$this->attr];
307
		$sql = str_format_n(
308
				"select distinct {link_table}.{bookID} as id".
309
				"from {table} inner join {link_table} on {table}.id = {link_table}.{link_join_on} ",
310
				$queryParams);
311
		return $sql;
312
	}
313
}