Passed
Push — master ( 6146c4...4b0cb0 )
by John
13:36
created

AllConfig   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 455
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 138
dl 0
loc 455
rs 6.4799
c 0
b 0
f 0
wmc 54

23 Methods

Rating   Name   Duplication   Size   Complexity  
A setSystemValue() 0 2 1
A deleteSystemValue() 0 2 1
A getAppKeys() 0 2 1
A getUserValueForUsers() 0 32 6
A deleteAllUserValues() 0 9 1
A getFilteredSystemValue() 0 2 1
B setUserValue() 0 50 11
A __construct() 0 3 1
A getUserValue() 0 6 3
A deleteAppFromAllUsers() 0 10 2
A getUsersForUserValue() 0 22 3
A deleteAppValues() 0 2 1
A deleteAppValue() 0 2 1
A getAppValue() 0 2 1
A setSystemValues() 0 2 1
A setAppValue() 0 2 1
A getSystemValue() 0 2 1
A fixDIInit() 0 3 2
A getUserValues() 0 24 6
A getUserKeys() 0 6 2
A deleteUserValue() 0 10 3
A getSystemConfig() 0 2 1
A getUsersForUserValueCaseInsensitive() 0 22 3

How to fix   Complexity   

Complex Class

Complex classes like AllConfig often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AllConfig, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Loki3000 <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Robin McCorkell <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC;
34
use OC\Cache\CappedMemoryCache;
35
use OCP\IDBConnection;
36
use OCP\PreConditionNotMetException;
37
38
/**
39
 * Class to combine all the configuration options ownCloud offers
40
 */
41
class AllConfig implements \OCP\IConfig {
42
	/** @var SystemConfig */
43
	private $systemConfig;
44
45
	/** @var IDBConnection */
46
	private $connection;
47
48
	/**
49
	 * 3 dimensional array with the following structure:
50
	 * [ $userId =>
51
	 *     [ $appId =>
52
	 *         [ $key => $value ]
53
	 *     ]
54
	 * ]
55
	 *
56
	 * database table: preferences
57
	 *
58
	 * methods that use this:
59
	 *   - setUserValue
60
	 *   - getUserValue
61
	 *   - getUserKeys
62
	 *   - deleteUserValue
63
	 *   - deleteAllUserValues
64
	 *   - deleteAppFromAllUsers
65
	 *
66
	 * @var CappedMemoryCache $userCache
67
	 */
68
	private $userCache;
69
70
	/**
71
	 * @param SystemConfig $systemConfig
72
	 */
73
	public function __construct(SystemConfig $systemConfig) {
74
		$this->userCache = new CappedMemoryCache();
75
		$this->systemConfig = $systemConfig;
76
	}
77
78
	/**
79
	 * TODO - FIXME This fixes an issue with base.php that cause cyclic
80
	 * dependencies, especially with autoconfig setup
81
	 *
82
	 * Replace this by properly injected database connection. Currently the
83
	 * base.php triggers the getDatabaseConnection too early which causes in
84
	 * autoconfig setup case a too early distributed database connection and
85
	 * the autoconfig then needs to reinit all already initialized dependencies
86
	 * that use the database connection.
87
	 *
88
	 * otherwise a SQLite database is created in the wrong directory
89
	 * because the database connection was created with an uninitialized config
90
	 */
91
	private function fixDIInit() {
92
		if($this->connection === null) {
93
			$this->connection = \OC::$server->getDatabaseConnection();
94
		}
95
	}
96
97
	/**
98
	 * Sets and deletes system wide values
99
	 *
100
	 * @param array $configs Associative array with `key => value` pairs
101
	 *                       If value is null, the config key will be deleted
102
	 */
103
	public function setSystemValues(array $configs) {
104
		$this->systemConfig->setValues($configs);
105
	}
106
107
	/**
108
	 * Sets a new system wide value
109
	 *
110
	 * @param string $key the key of the value, under which will be saved
111
	 * @param mixed $value the value that should be stored
112
	 */
113
	public function setSystemValue($key, $value) {
114
		$this->systemConfig->setValue($key, $value);
115
	}
116
117
	/**
118
	 * Looks up a system wide defined value
119
	 *
120
	 * @param string $key the key of the value, under which it was saved
121
	 * @param mixed $default the default value to be returned if the value isn't set
122
	 * @return mixed the value or $default
123
	 */
124
	public function getSystemValue($key, $default = '') {
125
		return $this->systemConfig->getValue($key, $default);
126
	}
127
128
	/**
129
	 * Looks up a system wide defined value and filters out sensitive data
130
	 *
131
	 * @param string $key the key of the value, under which it was saved
132
	 * @param mixed $default the default value to be returned if the value isn't set
133
	 * @return mixed the value or $default
134
	 */
135
	public function getFilteredSystemValue($key, $default = '') {
136
		return $this->systemConfig->getFilteredValue($key, $default);
137
	}
138
139
	/**
140
	 * Delete a system wide defined value
141
	 *
142
	 * @param string $key the key of the value, under which it was saved
143
	 */
144
	public function deleteSystemValue($key) {
145
		$this->systemConfig->deleteValue($key);
146
	}
147
148
	/**
149
	 * Get all keys stored for an app
150
	 *
151
	 * @param string $appName the appName that we stored the value under
152
	 * @return string[] the keys stored for the app
153
	 */
154
	public function getAppKeys($appName) {
155
		return \OC::$server->query(\OC\AppConfig::class)->getKeys($appName);
0 ignored issues
show
Bug introduced by
The method getKeys() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

155
		return \OC::$server->query(\OC\AppConfig::class)->/** @scrutinizer ignore-call */ getKeys($appName);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
156
	}
157
158
	/**
159
	 * Writes a new app wide value
160
	 *
161
	 * @param string $appName the appName that we want to store the value under
162
	 * @param string $key the key of the value, under which will be saved
163
	 * @param string|float|int $value the value that should be stored
164
	 */
165
	public function setAppValue($appName, $key, $value) {
166
		\OC::$server->query(\OC\AppConfig::class)->setValue($appName, $key, $value);
0 ignored issues
show
Bug introduced by
The method setValue() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
		\OC::$server->query(\OC\AppConfig::class)->/** @scrutinizer ignore-call */ setValue($appName, $key, $value);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
167
	}
168
169
	/**
170
	 * Looks up an app wide defined value
171
	 *
172
	 * @param string $appName the appName that we stored the value under
173
	 * @param string $key the key of the value, under which it was saved
174
	 * @param string $default the default value to be returned if the value isn't set
175
	 * @return string the saved value
176
	 */
177
	public function getAppValue($appName, $key, $default = '') {
178
		return \OC::$server->query(\OC\AppConfig::class)->getValue($appName, $key, $default);
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

178
		return \OC::$server->query(\OC\AppConfig::class)->/** @scrutinizer ignore-call */ getValue($appName, $key, $default);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
179
	}
180
181
	/**
182
	 * Delete an app wide defined value
183
	 *
184
	 * @param string $appName the appName that we stored the value under
185
	 * @param string $key the key of the value, under which it was saved
186
	 */
187
	public function deleteAppValue($appName, $key) {
188
		\OC::$server->query(\OC\AppConfig::class)->deleteKey($appName, $key);
0 ignored issues
show
Bug introduced by
The method deleteKey() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

188
		\OC::$server->query(\OC\AppConfig::class)->/** @scrutinizer ignore-call */ deleteKey($appName, $key);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
189
	}
190
191
	/**
192
	 * Removes all keys in appconfig belonging to the app
193
	 *
194
	 * @param string $appName the appName the configs are stored under
195
	 */
196
	public function deleteAppValues($appName) {
197
		\OC::$server->query(\OC\AppConfig::class)->deleteApp($appName);
0 ignored issues
show
Bug introduced by
The method deleteApp() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

197
		\OC::$server->query(\OC\AppConfig::class)->/** @scrutinizer ignore-call */ deleteApp($appName);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
198
	}
199
200
201
	/**
202
	 * Set a user defined value
203
	 *
204
	 * @param string $userId the userId of the user that we want to store the value under
205
	 * @param string $appName the appName that we want to store the value under
206
	 * @param string $key the key under which the value is being stored
207
	 * @param string|float|int $value the value that you want to store
208
	 * @param string $preCondition only update if the config value was previously the value passed as $preCondition
209
	 * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
210
	 * @throws \UnexpectedValueException when trying to store an unexpected value
211
	 */
212
	public function setUserValue($userId, $appName, $key, $value, $preCondition = null) {
213
		if (!is_int($value) && !is_float($value) && !is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
214
			throw new \UnexpectedValueException('Only integers, floats and strings are allowed as value');
215
		}
216
217
		// TODO - FIXME
218
		$this->fixDIInit();
219
220
		$prevValue = $this->getUserValue($userId, $appName, $key, null);
221
222
		if ($prevValue !== null) {
0 ignored issues
show
introduced by
The condition $prevValue !== null is always true.
Loading history...
223
			if ($prevValue === (string)$value) {
224
				return;
225
			} else if ($preCondition !== null && $prevValue !== (string)$preCondition) {
226
				throw new PreConditionNotMetException();
227
			} else {
228
				$qb = $this->connection->getQueryBuilder();
229
				$qb->update('preferences')
230
					->set('configvalue', $qb->createNamedParameter($value))
231
					->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)))
232
					->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($appName)))
233
					->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
234
				$qb->execute();
235
236
				$this->userCache[$userId][$appName][$key] = (string)$value;
237
				return;
238
			}
239
		}
240
241
		$preconditionArray = [];
242
		if (isset($preCondition)) {
243
			$preconditionArray = [
244
				'configvalue' => $preCondition,
245
			];
246
		}
247
248
		$this->connection->setValues('preferences', [
249
			'userid' => $userId,
250
			'appid' => $appName,
251
			'configkey' => $key,
252
		], [
253
			'configvalue' => $value,
254
		], $preconditionArray);
255
256
		// only add to the cache if we already loaded data for the user
257
		if (isset($this->userCache[$userId])) {
258
			if (!isset($this->userCache[$userId][$appName])) {
259
				$this->userCache[$userId][$appName] = array();
260
			}
261
			$this->userCache[$userId][$appName][$key] = (string)$value;
262
		}
263
	}
264
265
	/**
266
	 * Getting a user defined value
267
	 *
268
	 * @param string $userId the userId of the user that we want to store the value under
269
	 * @param string $appName the appName that we stored the value under
270
	 * @param string $key the key under which the value is being stored
271
	 * @param mixed $default the default value to be returned if the value isn't set
272
	 * @return string
273
	 */
274
	public function getUserValue($userId, $appName, $key, $default = '') {
275
		$data = $this->getUserValues($userId);
276
		if (isset($data[$appName]) and isset($data[$appName][$key])) {
277
			return $data[$appName][$key];
278
		} else {
279
			return $default;
280
		}
281
	}
282
283
	/**
284
	 * Get the keys of all stored by an app for the user
285
	 *
286
	 * @param string $userId the userId of the user that we want to store the value under
287
	 * @param string $appName the appName that we stored the value under
288
	 * @return string[]
289
	 */
290
	public function getUserKeys($userId, $appName) {
291
		$data = $this->getUserValues($userId);
292
		if (isset($data[$appName])) {
293
			return array_keys($data[$appName]);
294
		} else {
295
			return array();
296
		}
297
	}
298
299
	/**
300
	 * Delete a user value
301
	 *
302
	 * @param string $userId the userId of the user that we want to store the value under
303
	 * @param string $appName the appName that we stored the value under
304
	 * @param string $key the key under which the value is being stored
305
	 */
306
	public function deleteUserValue($userId, $appName, $key) {
307
		// TODO - FIXME
308
		$this->fixDIInit();
309
310
		$sql  = 'DELETE FROM `*PREFIX*preferences` '.
311
				'WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?';
312
		$this->connection->executeUpdate($sql, array($userId, $appName, $key));
313
314
		if (isset($this->userCache[$userId]) and isset($this->userCache[$userId][$appName])) {
315
			unset($this->userCache[$userId][$appName][$key]);
316
		}
317
	}
318
319
	/**
320
	 * Delete all user values
321
	 *
322
	 * @param string $userId the userId of the user that we want to remove all values from
323
	 */
324
	public function deleteAllUserValues($userId) {
325
		// TODO - FIXME
326
		$this->fixDIInit();
327
328
		$sql  = 'DELETE FROM `*PREFIX*preferences` '.
329
			'WHERE `userid` = ?';
330
		$this->connection->executeUpdate($sql, array($userId));
331
332
		unset($this->userCache[$userId]);
333
	}
334
335
	/**
336
	 * Delete all user related values of one app
337
	 *
338
	 * @param string $appName the appName of the app that we want to remove all values from
339
	 */
340
	public function deleteAppFromAllUsers($appName) {
341
		// TODO - FIXME
342
		$this->fixDIInit();
343
344
		$sql  = 'DELETE FROM `*PREFIX*preferences` '.
345
				'WHERE `appid` = ?';
346
		$this->connection->executeUpdate($sql, array($appName));
347
348
		foreach ($this->userCache as &$userCache) {
349
			unset($userCache[$appName]);
350
		}
351
	}
352
353
	/**
354
	 * Returns all user configs sorted by app of one user
355
	 *
356
	 * @param string $userId the user ID to get the app configs from
357
	 * @return array[] - 2 dimensional array with the following structure:
358
	 *     [ $appId =>
359
	 *         [ $key => $value ]
360
	 *     ]
361
	 */
362
	private function getUserValues($userId) {
363
		if (isset($this->userCache[$userId])) {
364
			return $this->userCache[$userId];
365
		}
366
		if ($userId === null || $userId === '') {
367
			$this->userCache[$userId]=array();
368
			return $this->userCache[$userId];
369
		}
370
371
		// TODO - FIXME
372
		$this->fixDIInit();
373
374
		$data = array();
375
		$query = 'SELECT `appid`, `configkey`, `configvalue` FROM `*PREFIX*preferences` WHERE `userid` = ?';
376
		$result = $this->connection->executeQuery($query, array($userId));
377
		while ($row = $result->fetch()) {
378
			$appId = $row['appid'];
379
			if (!isset($data[$appId])) {
380
				$data[$appId] = array();
381
			}
382
			$data[$appId][$row['configkey']] = $row['configvalue'];
383
		}
384
		$this->userCache[$userId] = $data;
385
		return $data;
386
	}
387
388
	/**
389
	 * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
390
	 *
391
	 * @param string $appName app to get the value for
392
	 * @param string $key the key to get the value for
393
	 * @param array $userIds the user IDs to fetch the values for
394
	 * @return array Mapped values: userId => value
395
	 */
396
	public function getUserValueForUsers($appName, $key, $userIds) {
397
		// TODO - FIXME
398
		$this->fixDIInit();
399
400
		if (empty($userIds) || !is_array($userIds)) {
401
			return array();
402
		}
403
404
		$chunkedUsers = array_chunk($userIds, 50, true);
405
		$placeholders50 = implode(',', array_fill(0, 50, '?'));
406
407
		$userValues = array();
408
		foreach ($chunkedUsers as $chunk) {
409
			$queryParams = $chunk;
410
			// create [$app, $key, $chunkedUsers]
411
			array_unshift($queryParams, $key);
412
			array_unshift($queryParams, $appName);
413
414
			$placeholders = (count($chunk) === 50) ? $placeholders50 :  implode(',', array_fill(0, count($chunk), '?'));
415
416
			$query    = 'SELECT `userid`, `configvalue` ' .
417
						'FROM `*PREFIX*preferences` ' .
418
						'WHERE `appid` = ? AND `configkey` = ? ' .
419
						'AND `userid` IN (' . $placeholders . ')';
420
			$result = $this->connection->executeQuery($query, $queryParams);
421
422
			while ($row = $result->fetch()) {
423
				$userValues[$row['userid']] = $row['configvalue'];
424
			}
425
		}
426
427
		return $userValues;
428
	}
429
430
	/**
431
	 * Determines the users that have the given value set for a specific app-key-pair
432
	 *
433
	 * @param string $appName the app to get the user for
434
	 * @param string $key the key to get the user for
435
	 * @param string $value the value to get the user for
436
	 * @return array of user IDs
437
	 */
438
	public function getUsersForUserValue($appName, $key, $value) {
439
		// TODO - FIXME
440
		$this->fixDIInit();
441
442
		$sql  = 'SELECT `userid` FROM `*PREFIX*preferences` ' .
443
				'WHERE `appid` = ? AND `configkey` = ? ';
444
445
		if($this->getSystemValue('dbtype', 'sqlite') === 'oci') {
446
			//oracle hack: need to explicitly cast CLOB to CHAR for comparison
447
			$sql .= 'AND to_char(`configvalue`) = ?';
448
		} else {
449
			$sql .= 'AND `configvalue` = ?';
450
		}
451
452
		$result = $this->connection->executeQuery($sql, array($appName, $key, $value));
453
454
		$userIDs = array();
455
		while ($row = $result->fetch()) {
456
			$userIDs[] = $row['userid'];
457
		}
458
459
		return $userIDs;
460
	}
461
462
	/**
463
	 * Determines the users that have the given value set for a specific app-key-pair
464
	 *
465
	 * @param string $appName the app to get the user for
466
	 * @param string $key the key to get the user for
467
	 * @param string $value the value to get the user for
468
	 * @return array of user IDs
469
	 */
470
	public function getUsersForUserValueCaseInsensitive($appName, $key, $value) {
471
		// TODO - FIXME
472
		$this->fixDIInit();
473
474
		$sql  = 'SELECT `userid` FROM `*PREFIX*preferences` ' .
475
			'WHERE `appid` = ? AND `configkey` = ? ';
476
477
		if($this->getSystemValue('dbtype', 'sqlite') === 'oci') {
478
			//oracle hack: need to explicitly cast CLOB to CHAR for comparison
479
			$sql .= 'AND LOWER(to_char(`configvalue`)) = LOWER(?)';
480
		} else {
481
			$sql .= 'AND LOWER(`configvalue`) = LOWER(?)';
482
		}
483
484
		$result = $this->connection->executeQuery($sql, array($appName, $key, $value));
485
486
		$userIDs = array();
487
		while ($row = $result->fetch()) {
488
			$userIDs[] = $row['userid'];
489
		}
490
491
		return $userIDs;
492
	}
493
494
	public function getSystemConfig() {
495
		return $this->systemConfig;
496
	}
497
}
498