Completed
Push — stable10 ( 737591...a2942c )
by Lukas
09:58 queued 09:42
created

Manager::setupAdminSettings()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 27
Code Lines 17

Duplication

Lines 27
Ratio 100 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 5
eloc 17
nc 5
nop 1
dl 27
loc 27
rs 8.439
c 3
b 1
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Arthur Schiwon <[email protected]>
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OC\Settings;
25
26
use OCP\AppFramework\QueryException;
27
use OCP\Encryption\IManager as EncryptionManager;
28
use OCP\IConfig;
29
use OCP\IDBConnection;
30
use OCP\IL10N;
31
use OCP\ILogger;
32
use OCP\IUserManager;
33
use OCP\Lock\ILockingProvider;
34
use OCP\Settings\ISettings;
35
use OCP\Settings\IManager;
36
use OCP\Settings\ISection;
37
38
class Manager implements IManager {
39
	const TABLE_ADMIN_SETTINGS = 'admin_settings';
40
	const TABLE_ADMIN_SECTIONS = 'admin_sections';
41
42
	/** @var ILogger */
43
	private $log;
44
	/** @var IDBConnection */
45
	private $dbc;
46
	/** @var IL10N */
47
	private $l;
48
	/** @var IConfig */
49
	private $config;
50
	/** @var EncryptionManager */
51
	private $encryptionManager;
52
	/** @var IUserManager */
53
	private $userManager;
54
	/** @var ILockingProvider */
55
	private $lockingProvider;
56
57
	/**
58
	 * @param ILogger $log
59
	 * @param IDBConnection $dbc
60
	 * @param IL10N $l
61
	 * @param IConfig $config
62
	 * @param EncryptionManager $encryptionManager
63
	 * @param IUserManager $userManager
64
	 * @param ILockingProvider $lockingProvider
65
	 */
66
	public function __construct(
67
		ILogger $log,
68
		IDBConnection $dbc,
69
		IL10N $l,
70
		IConfig $config,
71
		EncryptionManager $encryptionManager,
72
		IUserManager $userManager,
73
		ILockingProvider $lockingProvider
74
	) {
75
		$this->log = $log;
76
		$this->dbc = $dbc;
77
		$this->l = $l;
78
		$this->config = $config;
79
		$this->encryptionManager = $encryptionManager;
80
		$this->userManager = $userManager;
81
		$this->lockingProvider = $lockingProvider;
82
	}
83
84
	/**
85
	 * @inheritdoc
86
	 */
87
	public function setupSettings(array $settings) {
88
		if(isset($settings[IManager::KEY_ADMIN_SECTION])) {
89
			$this->setupAdminSection($settings[IManager::KEY_ADMIN_SECTION]);
90
		}
91
		if(isset($settings[IManager::KEY_ADMIN_SETTINGS])) {
92
			$this->setupAdminSettings($settings[IManager::KEY_ADMIN_SETTINGS]);
93
		}
94
	}
95
96
	/**
97
	 * attempts to remove an apps section and/or settings entry. A listener is
98
	 * added centrally making sure that this method is called ones an app was
99
	 * disabled.
100
	 *
101
	 * @param string $appId
102
	 * @since 9.1.0
103
	 */
104
	public function onAppDisabled($appId) {
105
		$appInfo = \OC_App::getAppInfo($appId); // hello static legacy
106
107 View Code Duplication
		if(isset($appInfo['settings'][IManager::KEY_ADMIN_SECTION])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
			$this->remove(self::TABLE_ADMIN_SECTIONS, $appInfo['settings'][IManager::KEY_ADMIN_SECTION]);
109
		}
110 View Code Duplication
		if(isset($settings['settings'][IManager::KEY_ADMIN_SETTINGS])) {
0 ignored issues
show
Bug introduced by
The variable $settings seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
			$this->remove(self::TABLE_ADMIN_SETTINGS, $appInfo['settings'][IManager::KEY_ADMIN_SETTINGS]);
112
		}
113
	}
114
115
	public function checkForOrphanedClassNames() {
116
		$tables = [ self::TABLE_ADMIN_SECTIONS, self::TABLE_ADMIN_SETTINGS ];
117 View Code Duplication
		foreach ($tables as $table) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
			$classes = $this->getClasses($table);
119
			foreach($classes as $className) {
120
				try {
121
					\OC::$server->query($className);
122
				} catch (QueryException $e) {
123
					$this->remove($table, $className);
124
				}
125
			}
126
		}
127
	}
128
129
	/**
130
	 * returns the registerd classes in the given table
131
	 *
132
	 * @param $table
133
	 * @return string[]
134
	 */
135
	private function getClasses($table) {
136
		$q = $this->dbc->getQueryBuilder();
137
		$resultStatement = $q->select('class')
138
			->from($table)
139
			->execute();
140
		$data = $resultStatement->fetchAll();
141
		$resultStatement->closeCursor();
142
143
		return array_map(function($row) { return $row['class']; }, $data);
144
	}
145
146
	/**
147
	 * @param string $sectionClassName
148
	 */
149 View Code Duplication
	private function setupAdminSection($sectionClassName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
		if(!class_exists($sectionClassName)) {
151
			$this->log->debug('Could not find admin section class ' . $sectionClassName);
152
			return;
153
		}
154
		try {
155
			$section = $this->query($sectionClassName);
156
		} catch (QueryException $e) {
157
			// cancel
158
			return;
159
		}
160
161
		if(!$section instanceof ISection) {
162
			$this->log->error(
163
				'Admin section instance must implement \OCP\ISection. Invalid class: {class}',
164
				['class' => $sectionClassName]
165
			);
166
			return;
167
		}
168
		if(!$this->hasAdminSection(get_class($section))) {
169
			$this->addAdminSection($section);
170
		} else {
171
			$this->updateAdminSection($section);
172
		}
173
	}
174
175 View Code Duplication
	private function addAdminSection(ISection $section) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
		$this->add(self::TABLE_ADMIN_SECTIONS, [
177
			'id' => $section->getID(),
178
			'class' => get_class($section),
179
			'priority' => $section->getPriority(),
180
		]);
181
	}
182
183 View Code Duplication
	private function addAdminSettings(ISettings $settings) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
		$this->add(self::TABLE_ADMIN_SETTINGS, [
185
			'class' => get_class($settings),
186
			'section' => $settings->getSection(),
187
			'priority' => $settings->getPriority(),
188
		]);
189
	}
190
191
	/**
192
	 * @param string $table
193
	 * @param array $values
194
	 */
195
	private function add($table, array $values) {
196
		$query = $this->dbc->getQueryBuilder();
197
		$values = array_map(function($value) use ($query) {
198
			return $query->createNamedParameter($value);
199
		}, $values);
200
		$query->insert($table)->values($values);
201
		$query->execute();
202
	}
203
204 View Code Duplication
	private function updateAdminSettings(ISettings $settings) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
205
		$this->update(
206
			self::TABLE_ADMIN_SETTINGS,
207
			'class',
208
			get_class($settings),
209
			[
210
				'section' => $settings->getSection(),
211
				'priority' => $settings->getPriority(),
212
			]
213
		);
214
	}
215
216 View Code Duplication
	private function updateAdminSection(ISection $section) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
217
		$this->update(
218
			self::TABLE_ADMIN_SECTIONS,
219
			'class',
220
			get_class($section),
221
			[
222
				'id'       => $section->getID(),
223
				'priority' => $section->getPriority(),
224
			]
225
		);
226
	}
227
228
	private function update($table, $idCol, $id, $values) {
229
		$query = $this->dbc->getQueryBuilder();
230
		$query->update($table);
231
		foreach($values as $key => $value) {
232
			$query->set($key, $query->createNamedParameter($value));
233
		}
234
		$query
235
			->where($query->expr()->eq($idCol, $query->createParameter($idCol)))
236
			->setParameter($idCol, $id)
237
			->execute();
238
	}
239
240
	/**
241
	 * @param string $className
242
	 * @return bool
243
	 */
244
	private function hasAdminSection($className) {
245
		return $this->has(self::TABLE_ADMIN_SECTIONS, $className);
246
	}
247
248
	/**
249
	 * @param string $className
250
	 * @return bool
251
	 */
252
	private function hasAdminSettings($className) {
253
		return $this->has(self::TABLE_ADMIN_SETTINGS, $className);
254
	}
255
256
	/**
257
	 * @param string $table
258
	 * @param string $className
259
	 * @return bool
260
	 */
261 View Code Duplication
	private function has($table, $className) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
262
		$query = $this->dbc->getQueryBuilder();
263
		$query->select('class')
264
			->from($table)
265
			->where($query->expr()->eq('class', $query->createNamedParameter($className)))
266
			->setMaxResults(1);
267
268
		$result = $query->execute();
269
		$row = $result->fetch();
270
		$result->closeCursor();
271
272
		return (bool) $row;
273
	}
274
275
	/**
276
	 * deletes an settings or admin entry from the given table
277
	 *
278
	 * @param $table
279
	 * @param $className
280
	 */
281 View Code Duplication
	private function remove($table, $className) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
		$query = $this->dbc->getQueryBuilder();
283
		$query->delete($table)
284
			->where($query->expr()->eq('class', $query->createNamedParameter($className)));
285
286
		$query->execute();
287
	}
288
289 View Code Duplication
	private function setupAdminSettings($settingsClassName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
290
		if(!class_exists($settingsClassName)) {
291
			$this->log->debug('Could not find admin section class ' . $settingsClassName);
292
			return;
293
		}
294
295
		try {
296
			/** @var ISettings $settings */
297
			$settings = $this->query($settingsClassName);
298
		} catch (QueryException $e) {
299
			// cancel
300
			return;
301
		}
302
303
		if(!$settings instanceof ISettings) {
304
			$this->log->error(
305
				'Admin section instance must implement \OCP\ISection. Invalid class: {class}',
306
				['class' => $settingsClassName]
307
			);
308
			return;
309
		}
310
		if(!$this->hasAdminSettings(get_class($settings))) {
311
			$this->addAdminSettings($settings);
312
		} else {
313
			$this->updateAdminSettings($settings);
314
		}
315
	}
316
317
	private function query($className) {
318
		try {
319
			return \OC::$server->query($className);
320
		} catch (QueryException $e) {
321
			$this->log->logException($e);
322
			throw $e;
323
		}
324
	}
325
326
	/**
327
	 * @inheritdoc
328
	 */
329
	public function getAdminSections() {
330
		$query = $this->dbc->getQueryBuilder();
331
		$query->select(['class', 'priority'])
332
			->from(self::TABLE_ADMIN_SECTIONS);
333
334
		// built-in sections
335
		$sections = [
336
			 0 => [new Section('server',        $this->l->t('Server settings'), 0)],
337
			 5 => [new Section('sharing',       $this->l->t('Sharing'), 0)],
338
			45 => [new Section('encryption',    $this->l->t('Encryption'), 0)],
339
			90 => [new Section('logging',       $this->l->t('Logging'), 0)],
340
			98 => [new Section('additional',    $this->l->t('Additional settings'), 0)],
341
			99 => [new Section('tips-tricks',   $this->l->t('Tips & tricks'), 0)],
342
		];
343
344
		$result = $query->execute();
345 View Code Duplication
		while($row = $result->fetch()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
			if(!isset($sections[$row['priority']])) {
347
				$sections[$row['priority']] = [];
348
			}
349
			try {
350
				$sections[$row['priority']][] = $this->query($row['class']);
351
			} catch (QueryException $e) {
352
				// skip
353
			}
354
		}
355
		$result->closeCursor();
356
357
		ksort($sections);
358
		return $sections;
359
	}
360
361
	private function getBuiltInAdminSettings($section) {
362
		$forms = [];
363
		try {
364
			if($section === 'server') {
365
				/** @var ISettings $form */
366
				$form = new Admin\Server($this->dbc, $this->config, $this->lockingProvider, $this->l);
367
				$forms[$form->getPriority()] = [$form];
368
			}
369
			if($section === 'encryption') {
370
				/** @var ISettings $form */
371
				$form = new Admin\Encryption($this->encryptionManager, $this->userManager);
0 ignored issues
show
Compatibility introduced by
$this->encryptionManager of type object<OCP\Encryption\IManager> is not a sub-type of object<OC\Encryption\Manager>. It seems like you assume a concrete implementation of the interface OCP\Encryption\IManager to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
372
				$forms[$form->getPriority()] = [$form];
373
			}
374
			if($section === 'sharing') {
375
				/** @var ISettings $form */
376
				$form = new Admin\Sharing($this->config);
377
				$forms[$form->getPriority()] = [$form];
378
			}
379
			if($section === 'logging') {
380
				/** @var ISettings $form */
381
				$form = new Admin\Logging($this->config);
382
				$forms[$form->getPriority()] = [$form];
383
			}
384
			if($section === 'additional') {
385
				/** @var ISettings $form */
386
				$form = new Admin\Additional($this->config);
387
				$forms[$form->getPriority()] = [$form];
388
			}
389
			if($section === 'tips-tricks') {
390
				/** @var ISettings $form */
391
				$form = new Admin\TipsTricks($this->config);
392
				$forms[$form->getPriority()] = [$form];
393
			}
394
		} catch (QueryException $e) {
395
			// skip
396
		}
397
		return $forms;
398
	}
399
400
	private function getAdminSettingsFromDB($section, &$settings) {
401
		$query = $this->dbc->getQueryBuilder();
402
		$query->select(['class', 'priority'])
403
			->from(self::TABLE_ADMIN_SETTINGS)
404
			->where($query->expr()->eq('section', $this->dbc->getQueryBuilder()->createParameter('section')))
405
			->setParameter('section', $section);
406
407
		$result = $query->execute();
408 View Code Duplication
		while($row = $result->fetch()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
409
			if(!isset($settings[$row['priority']])) {
410
				$settings[$row['priority']] = [];
411
			}
412
			try {
413
				$settings[$row['priority']][] = $this->query($row['class']);
414
			} catch (QueryException $e) {
415
				// skip
416
			}
417
		}
418
		$result->closeCursor();
419
420
		ksort($settings);
421
	}
422
423
	/**
424
	 * @inheritdoc
425
	 */
426
	public function getAdminSettings($section) {
427
		$settings = $this->getBuiltInAdminSettings($section);
428
		$this->getAdminSettingsFromDB($section, $settings);
429
		return $settings;
430
	}
431
}
432