Passed
Push — master ( b72d27...03dc79 )
by Roeland
21:59 queued 11:25
created

AppSettingsController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 12
nc 1
nop 13
dl 0
loc 25
rs 9.8666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2016, Lukas Reschke <[email protected]>
5
 *
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Felix A. Epp <[email protected]>
8
 * @author Jan-Christoph Borchardt <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Julius Härtl <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\Settings\Controller;
33
34
use OC\App\AppStore\Bundles\BundleFetcher;
35
use OC\App\AppStore\Fetcher\AppFetcher;
36
use OC\App\AppStore\Fetcher\CategoryFetcher;
37
use OC\App\AppStore\Version\VersionParser;
38
use OC\App\DependencyAnalyzer;
39
use OC\App\Platform;
40
use OC\Installer;
41
use OC_App;
42
use OCP\App\IAppManager;
43
use \OCP\AppFramework\Controller;
44
use OCP\AppFramework\Http;
45
use OCP\AppFramework\Http\ContentSecurityPolicy;
46
use OCP\AppFramework\Http\JSONResponse;
47
use OCP\AppFramework\Http\TemplateResponse;
48
use OCP\ILogger;
49
use OCP\INavigationManager;
50
use OCP\IRequest;
51
use OCP\IL10N;
52
use OCP\IConfig;
53
use OCP\IURLGenerator;
54
use OCP\L10N\IFactory;
55
56
/**
57
 * @package OC\Settings\Controller
58
 */
59
class AppSettingsController extends Controller {
60
61
	/** @var \OCP\IL10N */
62
	private $l10n;
63
	/** @var IConfig */
64
	private $config;
65
	/** @var INavigationManager */
66
	private $navigationManager;
67
	/** @var IAppManager */
68
	private $appManager;
69
	/** @var CategoryFetcher */
70
	private $categoryFetcher;
71
	/** @var AppFetcher */
72
	private $appFetcher;
73
	/** @var IFactory */
74
	private $l10nFactory;
75
	/** @var BundleFetcher */
76
	private $bundleFetcher;
77
	/** @var Installer */
78
	private $installer;
79
	/** @var IURLGenerator */
80
	private $urlGenerator;
81
	/** @var ILogger */
82
	private $logger;
83
84
	/** @var array */
85
	private $allApps = [];
86
87
	/**
88
	 * @param string $appName
89
	 * @param IRequest $request
90
	 * @param IL10N $l10n
91
	 * @param IConfig $config
92
	 * @param INavigationManager $navigationManager
93
	 * @param IAppManager $appManager
94
	 * @param CategoryFetcher $categoryFetcher
95
	 * @param AppFetcher $appFetcher
96
	 * @param IFactory $l10nFactory
97
	 * @param BundleFetcher $bundleFetcher
98
	 * @param Installer $installer
99
	 * @param IURLGenerator $urlGenerator
100
	 * @param ILogger $logger
101
	 */
102
	public function __construct(string $appName,
103
								IRequest $request,
104
								IL10N $l10n,
105
								IConfig $config,
106
								INavigationManager $navigationManager,
107
								IAppManager $appManager,
108
								CategoryFetcher $categoryFetcher,
109
								AppFetcher $appFetcher,
110
								IFactory $l10nFactory,
111
								BundleFetcher $bundleFetcher,
112
								Installer $installer,
113
								IURLGenerator $urlGenerator,
114
								ILogger $logger) {
115
		parent::__construct($appName, $request);
116
		$this->l10n = $l10n;
117
		$this->config = $config;
118
		$this->navigationManager = $navigationManager;
119
		$this->appManager = $appManager;
120
		$this->categoryFetcher = $categoryFetcher;
121
		$this->appFetcher = $appFetcher;
122
		$this->l10nFactory = $l10nFactory;
123
		$this->bundleFetcher = $bundleFetcher;
124
		$this->installer = $installer;
125
		$this->urlGenerator = $urlGenerator;
126
		$this->logger = $logger;
127
	}
128
129
	/**
130
	 * @NoCSRFRequired
131
	 *
132
	 * @return TemplateResponse
133
	 */
134
	public function viewApps(): TemplateResponse {
135
		\OC_Util::addScript('settings', 'apps');
136
		$params = [];
137
		$params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
138
		$params['updateCount'] = count($this->getAppsWithUpdates());
139
		$params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
140
		$params['bundles'] = $this->getBundles();
141
		$this->navigationManager->setActiveEntry('core_apps');
142
143
		$templateResponse = new TemplateResponse('settings', 'settings-vue', ['serverData' => $params]);
144
		$policy = new ContentSecurityPolicy();
145
		$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
146
		$templateResponse->setContentSecurityPolicy($policy);
147
148
		return $templateResponse;
149
	}
150
151
	private function getAppsWithUpdates() {
152
		$appClass = new \OC_App();
153
		$apps = $appClass->listAllApps();
154
		foreach($apps as $key => $app) {
155
			$newVersion = $this->installer->isUpdateAvailable($app['id']);
156
			if($newVersion === false) {
157
				unset($apps[$key]);
158
			}
159
		}
160
		return $apps;
161
	}
162
163
	private function getBundles() {
164
		$result = [];
165
		$bundles = $this->bundleFetcher->getBundles();
166
		foreach ($bundles as $bundle) {
167
			$result[] = [
168
				'name' => $bundle->getName(),
169
				'id' => $bundle->getIdentifier(),
170
				'appIdentifiers' => $bundle->getAppIdentifiers()
171
			];
172
		}
173
		return $result;
174
175
	}
176
177
	/**
178
	 * Get all available categories
179
	 *
180
	 * @return JSONResponse
181
	 */
182
	public function listCategories(): JSONResponse {
183
		return new JSONResponse($this->getAllCategories());
184
	}
185
186
	private function getAllCategories() {
187
		$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
188
189
		$formattedCategories = [];
190
		$categories = $this->categoryFetcher->get();
191
		foreach($categories as $category) {
192
			$formattedCategories[] = [
193
				'id' => $category['id'],
194
				'ident' => $category['id'],
195
				'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
196
			];
197
		}
198
199
		return $formattedCategories;
200
	}
201
202
	private function fetchApps() {
203
		$appClass = new \OC_App();
204
		$apps = $appClass->listAllApps();
205
		foreach ($apps as $app) {
206
			$app['installed'] = true;
207
			$this->allApps[$app['id']] = $app;
208
		}
209
210
		$apps = $this->getAppsForCategory('');
211
		foreach ($apps as $app) {
212
			$app['appstore'] = true;
213
			if (!array_key_exists($app['id'], $this->allApps)) {
214
				$this->allApps[$app['id']] = $app;
215
			} else {
216
				$this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
217
			}
218
		}
219
220
		// add bundle information
221
		$bundles = $this->bundleFetcher->getBundles();
222
		foreach($bundles as $bundle) {
223
			foreach($bundle->getAppIdentifiers() as $identifier) {
224
				foreach($this->allApps as &$app) {
225
					if($app['id'] === $identifier) {
226
						$app['bundleId'] = $bundle->getIdentifier();
227
						continue;
228
					}
229
				}
230
			}
231
		}
232
	}
233
234
	private function getAllApps() {
235
		return $this->allApps;
236
	}
237
	/**
238
	 * Get all available apps in a category
239
	 *
240
	 * @param string $category
241
	 * @return JSONResponse
242
	 * @throws \Exception
243
	 */
244
	public function listApps(): JSONResponse {
245
246
		$this->fetchApps();
247
		$apps = $this->getAllApps();
248
249
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
250
251
		// Extend existing app details
252
		$apps = array_map(function($appData) use ($dependencyAnalyzer) {
253
			if (isset($appData['appstoreData'])) {
254
				$appstoreData = $appData['appstoreData'];
255
				$appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($appstoreData['screenshots'][0]['url']) : '';
256
				$appData['category'] = $appstoreData['categories'];
257
			}
258
259
			$newVersion = $this->installer->isUpdateAvailable($appData['id']);
260
			if($newVersion) {
261
				$appData['update'] = $newVersion;
262
			}
263
264
			// fix groups to be an array
265
			$groups = array();
266
			if (is_string($appData['groups'])) {
267
				$groups = json_decode($appData['groups']);
268
			}
269
			$appData['groups'] = $groups;
270
			$appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
271
272
			// fix licence vs license
273
			if (isset($appData['license']) && !isset($appData['licence'])) {
274
				$appData['licence'] = $appData['license'];
275
			}
276
277
			$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
278
			$ignoreMax = in_array($appData['id'], $ignoreMaxApps);
279
280
			// analyse dependencies
281
			$missing = $dependencyAnalyzer->analyze($appData, $ignoreMax);
282
			$appData['canInstall'] = empty($missing);
283
			$appData['missingDependencies'] = $missing;
284
285
			$appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
286
			$appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
287
			$appData['isCompatible'] = $dependencyAnalyzer->isMarkedCompatible($appData);
288
289
			return $appData;
290
		}, $apps);
291
292
		usort($apps, [$this, 'sortApps']);
293
294
		return new JSONResponse(['apps' => $apps, 'status' => 'success']);
295
	}
296
297
	/**
298
	 * Get all apps for a category from the app store
299
	 *
300
	 * @param string $requestedCategory
301
	 * @return array
302
	 * @throws \Exception
303
	 */
304
	private function getAppsForCategory($requestedCategory = ''): array {
305
		$versionParser = new VersionParser();
306
		$formattedApps = [];
307
		$apps = $this->appFetcher->get();
308
		foreach($apps as $app) {
309
			// Skip all apps not in the requested category
310
			if ($requestedCategory !== '') {
311
				$isInCategory = false;
312
				foreach($app['categories'] as $category) {
313
					if($category === $requestedCategory) {
314
						$isInCategory = true;
315
					}
316
				}
317
				if(!$isInCategory) {
318
					continue;
319
				}
320
			}
321
322
			if (!isset($app['releases'][0]['rawPlatformVersionSpec'])) {
323
				continue;
324
			}
325
			$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
326
			$nextCloudVersionDependencies = [];
327
			if($nextCloudVersion->getMinimumVersion() !== '') {
328
				$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
329
			}
330
			if($nextCloudVersion->getMaximumVersion() !== '') {
331
				$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
332
			}
333
			$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
334
			$existsLocally = \OC_App::getAppPath($app['id']) !== false;
335
			$phpDependencies = [];
336
			if($phpVersion->getMinimumVersion() !== '') {
337
				$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
338
			}
339
			if($phpVersion->getMaximumVersion() !== '') {
340
				$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
341
			}
342
			if(isset($app['releases'][0]['minIntSize'])) {
343
				$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
344
			}
345
			$authors = '';
346
			foreach($app['authors'] as $key => $author) {
347
				$authors .= $author['name'];
348
				if($key !== count($app['authors']) - 1) {
349
					$authors .= ', ';
350
				}
351
			}
352
353
			$currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
354
			$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
355
			$groups = null;
356
			if($enabledValue !== 'no' && $enabledValue !== 'yes') {
357
				$groups = $enabledValue;
358
			}
359
360
			$currentVersion = '';
361
			if($this->appManager->isInstalled($app['id'])) {
362
				$currentVersion = $this->appManager->getAppVersion($app['id']);
363
			} else {
364
				$currentLanguage = $app['releases'][0]['version'];
365
			}
366
367
			$formattedApps[] = [
368
				'id' => $app['id'],
369
				'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
370
				'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
371
				'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
372
				'license' => $app['releases'][0]['licenses'],
373
				'author' => $authors,
374
				'shipped' => false,
375
				'version' => $currentVersion,
376
				'default_enable' => '',
377
				'types' => [],
378
				'documentation' => [
379
					'admin' => $app['adminDocs'],
380
					'user' => $app['userDocs'],
381
					'developer' => $app['developerDocs']
382
				],
383
				'website' => $app['website'],
384
				'bugs' => $app['issueTracker'],
385
				'detailpage' => $app['website'],
386
				'dependencies' => array_merge(
387
					$nextCloudVersionDependencies,
388
					$phpDependencies
389
				),
390
				'level' => ($app['isFeatured'] === true) ? 200 : 100,
391
				'missingMaxOwnCloudVersion' => false,
392
				'missingMinOwnCloudVersion' => false,
393
				'canInstall' => true,
394
				'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
395
				'score' => $app['ratingOverall'],
396
				'ratingNumOverall' => $app['ratingNumOverall'],
397
				'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
398
				'removable' => $existsLocally,
399
				'active' => $this->appManager->isEnabledForUser($app['id']),
400
				'needsDownload' => !$existsLocally,
401
				'groups' => $groups,
402
				'fromAppStore' => true,
403
				'appstoreData' => $app,
404
			];
405
		}
406
407
		return $formattedApps;
408
	}
409
410
	/**
411
	 * @PasswordConfirmationRequired
412
	 *
413
	 * @param string $appId
414
	 * @param array $groups
415
	 * @return JSONResponse
416
	 */
417
	public function enableApp(string $appId, array $groups = []): JSONResponse {
418
		return $this->enableApps([$appId], $groups);
419
	}
420
421
	/**
422
	 * Enable one or more apps
423
	 *
424
	 * apps will be enabled for specific groups only if $groups is defined
425
	 *
426
	 * @PasswordConfirmationRequired
427
	 * @param array $appIds
428
	 * @param array $groups
429
	 * @return JSONResponse
430
	 */
431
	public function enableApps(array $appIds, array $groups = []): JSONResponse {
432
		try {
433
			$updateRequired = false;
434
435
			foreach ($appIds as $appId) {
436
				$appId = OC_App::cleanAppId($appId);
437
438
				// Check if app is already downloaded
439
				/** @var Installer $installer */
440
				$installer = \OC::$server->query(Installer::class);
441
				$isDownloaded = $installer->isDownloaded($appId);
442
443
				if(!$isDownloaded) {
444
					$installer->downloadApp($appId);
445
				}
446
447
				$installer->installApp($appId);
448
449
				if (count($groups) > 0) {
450
					$this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
451
				} else {
452
					$this->appManager->enableApp($appId);
453
				}
454
				if (\OC_App::shouldUpgrade($appId)) {
455
					$updateRequired = true;
456
				}
457
			}
458
			return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
459
460
		} catch (\Exception $e) {
461
			$this->logger->logException($e);
462
			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
463
		}
464
	}
465
466
	private function getGroupList(array $groups) {
467
		$groupManager = \OC::$server->getGroupManager();
468
		$groupsList = [];
469
		foreach ($groups as $group) {
470
			$groupItem = $groupManager->get($group);
471
			if ($groupItem instanceof \OCP\IGroup) {
472
				$groupsList[] = $groupManager->get($group);
473
			}
474
		}
475
		return $groupsList;
476
	}
477
478
	/**
479
	 * @PasswordConfirmationRequired
480
	 *
481
	 * @param string $appId
482
	 * @return JSONResponse
483
	 */
484
	public function disableApp(string $appId): JSONResponse {
485
		return $this->disableApps([$appId]);
486
	}
487
488
	/**
489
	 * @PasswordConfirmationRequired
490
	 *
491
	 * @param array $appIds
492
	 * @return JSONResponse
493
	 */
494
	public function disableApps(array $appIds): JSONResponse {
495
		try {
496
			foreach ($appIds as $appId) {
497
				$appId = OC_App::cleanAppId($appId);
498
				$this->appManager->disableApp($appId);
499
			}
500
			return new JSONResponse([]);
501
		} catch (\Exception $e) {
502
			$this->logger->logException($e);
503
			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
504
		}
505
	}
506
507
	/**
508
	 * @PasswordConfirmationRequired
509
	 *
510
	 * @param string $appId
511
	 * @return JSONResponse
512
	 */
513
	public function uninstallApp(string $appId): JSONResponse {
514
		$appId = OC_App::cleanAppId($appId);
515
		$result = $this->installer->removeApp($appId);
516
		if($result !== false) {
517
			$this->appManager->clearAppsCache();
518
			return new JSONResponse(['data' => ['appid' => $appId]]);
519
		}
520
		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
521
	}
522
523
	/**
524
	 * @param string $appId
525
	 * @return JSONResponse
526
	 */
527
	public function updateApp(string $appId): JSONResponse {
528
		$appId = OC_App::cleanAppId($appId);
529
530
		$this->config->setSystemValue('maintenance', true);
531
		try {
532
			$result = $this->installer->updateAppstoreApp($appId);
533
			$this->config->setSystemValue('maintenance', false);
534
		} catch (\Exception $ex) {
535
			$this->config->setSystemValue('maintenance', false);
536
			return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
537
		}
538
539
		if ($result !== false) {
540
			return new JSONResponse(['data' => ['appid' => $appId]]);
541
		}
542
		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
543
	}
544
545
	private function sortApps($a, $b) {
546
		$a = (string)$a['name'];
547
		$b = (string)$b['name'];
548
		if ($a === $b) {
549
			return 0;
550
		}
551
		return ($a < $b) ? -1 : 1;
552
	}
553
554
	public function force(string $appId): JSONResponse {
555
		$appId = OC_App::cleanAppId($appId);
556
557
		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
558
		if (!in_array($appId, $ignoreMaxApps, true)) {
559
			$ignoreMaxApps[] = $appId;
560
			$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
561
		}
562
563
		return new JSONResponse();
564
	}
565
566
}
567