Completed
Push — master ( 94c35d...8d08cf )
by Roeland
16:44
created
lib/private/Installer.php 2 patches
Indentation   +548 added lines, -548 removed lines patch added patch discarded remove patch
@@ -52,552 +52,552 @@
 block discarded – undo
52 52
  * This class provides the functionality needed to install, update and remove apps
53 53
  */
54 54
 class Installer {
55
-	/** @var AppFetcher */
56
-	private $appFetcher;
57
-	/** @var IClientService */
58
-	private $clientService;
59
-	/** @var ITempManager */
60
-	private $tempManager;
61
-	/** @var ILogger */
62
-	private $logger;
63
-	/** @var IConfig */
64
-	private $config;
65
-	/** @var array - for caching the result of app fetcher */
66
-	private $apps = null;
67
-	/** @var bool|null - for caching the result of the ready status */
68
-	private $isInstanceReadyForUpdates = null;
69
-
70
-	/**
71
-	 * @param AppFetcher $appFetcher
72
-	 * @param IClientService $clientService
73
-	 * @param ITempManager $tempManager
74
-	 * @param ILogger $logger
75
-	 * @param IConfig $config
76
-	 */
77
-	public function __construct(AppFetcher $appFetcher,
78
-								IClientService $clientService,
79
-								ITempManager $tempManager,
80
-								ILogger $logger,
81
-								IConfig $config) {
82
-		$this->appFetcher = $appFetcher;
83
-		$this->clientService = $clientService;
84
-		$this->tempManager = $tempManager;
85
-		$this->logger = $logger;
86
-		$this->config = $config;
87
-	}
88
-
89
-	/**
90
-	 * Installs an app that is located in one of the app folders already
91
-	 *
92
-	 * @param string $appId App to install
93
-	 * @throws \Exception
94
-	 * @return string app ID
95
-	 */
96
-	public function installApp($appId) {
97
-		$app = \OC_App::findAppInDirectories($appId);
98
-		if($app === false) {
99
-			throw new \Exception('App not found in any app directory');
100
-		}
101
-
102
-		$basedir = $app['path'].'/'.$appId;
103
-		$info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
104
-
105
-		$l = \OC::$server->getL10N('core');
106
-
107
-		if(!is_array($info)) {
108
-			throw new \Exception(
109
-				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
110
-					[$appId]
111
-				)
112
-			);
113
-		}
114
-
115
-		$version = implode('.', \OCP\Util::getVersion());
116
-		if (!\OC_App::isAppCompatible($version, $info)) {
117
-			throw new \Exception(
118
-				// TODO $l
119
-				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
120
-					[$info['name']]
121
-				)
122
-			);
123
-		}
124
-
125
-		// check for required dependencies
126
-		\OC_App::checkAppDependencies($this->config, $l, $info);
127
-		\OC_App::registerAutoloading($appId, $basedir);
128
-
129
-		//install the database
130
-		if(is_file($basedir.'/appinfo/database.xml')) {
131
-			if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
132
-				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
133
-			} else {
134
-				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
135
-			}
136
-		} else {
137
-			$ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
138
-			$ms->migrate();
139
-		}
140
-
141
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
142
-
143
-		//run appinfo/install.php
144
-		self::includeAppScript($basedir . '/appinfo/install.php');
145
-
146
-		$appData = OC_App::getAppInfo($appId);
147
-		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
148
-
149
-		//set the installed version
150
-		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
151
-		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
152
-
153
-		//set remote/public handlers
154
-		foreach($info['remote'] as $name=>$path) {
155
-			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
156
-		}
157
-		foreach($info['public'] as $name=>$path) {
158
-			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
159
-		}
160
-
161
-		OC_App::setAppTypes($info['id']);
162
-
163
-		return $info['id'];
164
-	}
165
-
166
-	/**
167
-	 * Updates the specified app from the appstore
168
-	 *
169
-	 * @param string $appId
170
-	 * @return bool
171
-	 */
172
-	public function updateAppstoreApp($appId) {
173
-		if($this->isUpdateAvailable($appId)) {
174
-			try {
175
-				$this->downloadApp($appId);
176
-			} catch (\Exception $e) {
177
-				$this->logger->logException($e, [
178
-					'level' => ILogger::ERROR,
179
-					'app' => 'core',
180
-				]);
181
-				return false;
182
-			}
183
-			return OC_App::updateApp($appId);
184
-		}
185
-
186
-		return false;
187
-	}
188
-
189
-	/**
190
-	 * Downloads an app and puts it into the app directory
191
-	 *
192
-	 * @param string $appId
193
-	 *
194
-	 * @throws \Exception If the installation was not successful
195
-	 */
196
-	public function downloadApp($appId) {
197
-		$appId = strtolower($appId);
198
-
199
-		$apps = $this->appFetcher->get();
200
-		foreach($apps as $app) {
201
-			if($app['id'] === $appId) {
202
-				// Load the certificate
203
-				$certificate = new X509();
204
-				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
205
-				$loadedCertificate = $certificate->loadX509($app['certificate']);
206
-
207
-				// Verify if the certificate has been revoked
208
-				$crl = new X509();
209
-				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
210
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
211
-				if($crl->validateSignature() !== true) {
212
-					throw new \Exception('Could not validate CRL signature');
213
-				}
214
-				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
215
-				$revoked = $crl->getRevoked($csn);
216
-				if ($revoked !== false) {
217
-					throw new \Exception(
218
-						sprintf(
219
-							'Certificate "%s" has been revoked',
220
-							$csn
221
-						)
222
-					);
223
-				}
224
-
225
-				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
226
-				if($certificate->validateSignature() !== true) {
227
-					throw new \Exception(
228
-						sprintf(
229
-							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
230
-							$appId
231
-						)
232
-					);
233
-				}
234
-
235
-				// Verify if the certificate is issued for the requested app id
236
-				$certInfo = openssl_x509_parse($app['certificate']);
237
-				if(!isset($certInfo['subject']['CN'])) {
238
-					throw new \Exception(
239
-						sprintf(
240
-							'App with id %s has a cert with no CN',
241
-							$appId
242
-						)
243
-					);
244
-				}
245
-				if($certInfo['subject']['CN'] !== $appId) {
246
-					throw new \Exception(
247
-						sprintf(
248
-							'App with id %s has a cert issued to %s',
249
-							$appId,
250
-							$certInfo['subject']['CN']
251
-						)
252
-					);
253
-				}
254
-
255
-				// Download the release
256
-				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
257
-				$client = $this->clientService->newClient();
258
-				$client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
259
-
260
-				// Check if the signature actually matches the downloaded content
261
-				$certificate = openssl_get_publickey($app['certificate']);
262
-				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
263
-				openssl_free_key($certificate);
264
-
265
-				if($verified === true) {
266
-					// Seems to match, let's proceed
267
-					$extractDir = $this->tempManager->getTemporaryFolder();
268
-					$archive = new TAR($tempFile);
269
-
270
-					if($archive) {
271
-						if (!$archive->extract($extractDir)) {
272
-							throw new \Exception(
273
-								sprintf(
274
-									'Could not extract app %s',
275
-									$appId
276
-								)
277
-							);
278
-						}
279
-						$allFiles = scandir($extractDir);
280
-						$folders = array_diff($allFiles, ['.', '..']);
281
-						$folders = array_values($folders);
282
-
283
-						if(count($folders) > 1) {
284
-							throw new \Exception(
285
-								sprintf(
286
-									'Extracted app %s has more than 1 folder',
287
-									$appId
288
-								)
289
-							);
290
-						}
291
-
292
-						// Check if appinfo/info.xml has the same app ID as well
293
-						$loadEntities = libxml_disable_entity_loader(false);
294
-						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
295
-						libxml_disable_entity_loader($loadEntities);
296
-						if((string)$xml->id !== $appId) {
297
-							throw new \Exception(
298
-								sprintf(
299
-									'App for id %s has a wrong app ID in info.xml: %s',
300
-									$appId,
301
-									(string)$xml->id
302
-								)
303
-							);
304
-						}
305
-
306
-						// Check if the version is lower than before
307
-						$currentVersion = OC_App::getAppVersion($appId);
308
-						$newVersion = (string)$xml->version;
309
-						if(version_compare($currentVersion, $newVersion) === 1) {
310
-							throw new \Exception(
311
-								sprintf(
312
-									'App for id %s has version %s and tried to update to lower version %s',
313
-									$appId,
314
-									$currentVersion,
315
-									$newVersion
316
-								)
317
-							);
318
-						}
319
-
320
-						$baseDir = OC_App::getInstallPath() . '/' . $appId;
321
-						// Remove old app with the ID if existent
322
-						OC_Helper::rmdirr($baseDir);
323
-						// Move to app folder
324
-						if(@mkdir($baseDir)) {
325
-							$extractDir .= '/' . $folders[0];
326
-							OC_Helper::copyr($extractDir, $baseDir);
327
-						}
328
-						OC_Helper::copyr($extractDir, $baseDir);
329
-						OC_Helper::rmdirr($extractDir);
330
-						return;
331
-					} else {
332
-						throw new \Exception(
333
-							sprintf(
334
-								'Could not extract app with ID %s to %s',
335
-								$appId,
336
-								$extractDir
337
-							)
338
-						);
339
-					}
340
-				} else {
341
-					// Signature does not match
342
-					throw new \Exception(
343
-						sprintf(
344
-							'App with id %s has invalid signature',
345
-							$appId
346
-						)
347
-					);
348
-				}
349
-			}
350
-		}
351
-
352
-		throw new \Exception(
353
-			sprintf(
354
-				'Could not download app %s',
355
-				$appId
356
-			)
357
-		);
358
-	}
359
-
360
-	/**
361
-	 * Check if an update for the app is available
362
-	 *
363
-	 * @param string $appId
364
-	 * @return string|false false or the version number of the update
365
-	 */
366
-	public function isUpdateAvailable($appId) {
367
-		if ($this->isInstanceReadyForUpdates === null) {
368
-			$installPath = OC_App::getInstallPath();
369
-			if ($installPath === false || $installPath === null) {
370
-				$this->isInstanceReadyForUpdates = false;
371
-			} else {
372
-				$this->isInstanceReadyForUpdates = true;
373
-			}
374
-		}
375
-
376
-		if ($this->isInstanceReadyForUpdates === false) {
377
-			return false;
378
-		}
379
-
380
-		if ($this->isInstalledFromGit($appId) === true) {
381
-			return false;
382
-		}
383
-
384
-		if ($this->apps === null) {
385
-			$this->apps = $this->appFetcher->get();
386
-		}
387
-
388
-		foreach($this->apps as $app) {
389
-			if($app['id'] === $appId) {
390
-				$currentVersion = OC_App::getAppVersion($appId);
391
-				$newestVersion = $app['releases'][0]['version'];
392
-				if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
393
-					return $newestVersion;
394
-				} else {
395
-					return false;
396
-				}
397
-			}
398
-		}
399
-
400
-		return false;
401
-	}
402
-
403
-	/**
404
-	 * Check if app has been installed from git
405
-	 * @param string $name name of the application to remove
406
-	 * @return boolean
407
-	 *
408
-	 * The function will check if the path contains a .git folder
409
-	 */
410
-	private function isInstalledFromGit($appId) {
411
-		$app = \OC_App::findAppInDirectories($appId);
412
-		if($app === false) {
413
-			return false;
414
-		}
415
-		$basedir = $app['path'].'/'.$appId;
416
-		return file_exists($basedir.'/.git/');
417
-	}
418
-
419
-	/**
420
-	 * Check if app is already downloaded
421
-	 * @param string $name name of the application to remove
422
-	 * @return boolean
423
-	 *
424
-	 * The function will check if the app is already downloaded in the apps repository
425
-	 */
426
-	public function isDownloaded($name) {
427
-		foreach(\OC::$APPSROOTS as $dir) {
428
-			$dirToTest  = $dir['path'];
429
-			$dirToTest .= '/';
430
-			$dirToTest .= $name;
431
-			$dirToTest .= '/';
432
-
433
-			if (is_dir($dirToTest)) {
434
-				return true;
435
-			}
436
-		}
437
-
438
-		return false;
439
-	}
440
-
441
-	/**
442
-	 * Removes an app
443
-	 * @param string $appId ID of the application to remove
444
-	 * @return boolean
445
-	 *
446
-	 *
447
-	 * This function works as follows
448
-	 *   -# call uninstall repair steps
449
-	 *   -# removing the files
450
-	 *
451
-	 * The function will not delete preferences, tables and the configuration,
452
-	 * this has to be done by the function oc_app_uninstall().
453
-	 */
454
-	public function removeApp($appId) {
455
-		if($this->isDownloaded( $appId )) {
456
-			if (\OC::$server->getAppManager()->isShipped($appId)) {
457
-				return false;
458
-			}
459
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
460
-			OC_Helper::rmdirr($appDir);
461
-			return true;
462
-		}else{
463
-			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR);
464
-
465
-			return false;
466
-		}
467
-
468
-	}
469
-
470
-	/**
471
-	 * Installs the app within the bundle and marks the bundle as installed
472
-	 *
473
-	 * @param Bundle $bundle
474
-	 * @throws \Exception If app could not get installed
475
-	 */
476
-	public function installAppBundle(Bundle $bundle) {
477
-		$appIds = $bundle->getAppIdentifiers();
478
-		foreach($appIds as $appId) {
479
-			if(!$this->isDownloaded($appId)) {
480
-				$this->downloadApp($appId);
481
-			}
482
-			$this->installApp($appId);
483
-			$app = new OC_App();
484
-			$app->enable($appId);
485
-		}
486
-		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
487
-		$bundles[] = $bundle->getIdentifier();
488
-		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
489
-	}
490
-
491
-	/**
492
-	 * Installs shipped apps
493
-	 *
494
-	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
495
-	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
496
-	 *                         working ownCloud at the end instead of an aborted update.
497
-	 * @return array Array of error messages (appid => Exception)
498
-	 */
499
-	public static function installShippedApps($softErrors = false) {
500
-		$appManager = \OC::$server->getAppManager();
501
-		$config = \OC::$server->getConfig();
502
-		$errors = [];
503
-		foreach(\OC::$APPSROOTS as $app_dir) {
504
-			if($dir = opendir( $app_dir['path'] )) {
505
-				while( false !== ( $filename = readdir( $dir ))) {
506
-					if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
507
-						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
508
-							if($config->getAppValue($filename, "installed_version", null) === null) {
509
-								$info=OC_App::getAppInfo($filename);
510
-								$enabled = isset($info['default_enable']);
511
-								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
512
-									  && $config->getAppValue($filename, 'enabled') !== 'no') {
513
-									if ($softErrors) {
514
-										try {
515
-											Installer::installShippedApp($filename);
516
-										} catch (HintException $e) {
517
-											if ($e->getPrevious() instanceof TableExistsException) {
518
-												$errors[$filename] = $e;
519
-												continue;
520
-											}
521
-											throw $e;
522
-										}
523
-									} else {
524
-										Installer::installShippedApp($filename);
525
-									}
526
-									$config->setAppValue($filename, 'enabled', 'yes');
527
-								}
528
-							}
529
-						}
530
-					}
531
-				}
532
-				closedir( $dir );
533
-			}
534
-		}
535
-
536
-		return $errors;
537
-	}
538
-
539
-	/**
540
-	 * install an app already placed in the app folder
541
-	 * @param string $app id of the app to install
542
-	 * @return integer
543
-	 */
544
-	public static function installShippedApp($app) {
545
-		//install the database
546
-		$appPath = OC_App::getAppPath($app);
547
-		\OC_App::registerAutoloading($app, $appPath);
548
-
549
-		if(is_file("$appPath/appinfo/database.xml")) {
550
-			try {
551
-				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
552
-			} catch (TableExistsException $e) {
553
-				throw new HintException(
554
-					'Failed to enable app ' . $app,
555
-					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
556
-					0, $e
557
-				);
558
-			}
559
-		} else {
560
-			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
561
-			$ms->migrate();
562
-		}
563
-
564
-		//run appinfo/install.php
565
-		self::includeAppScript("$appPath/appinfo/install.php");
566
-
567
-		$info = OC_App::getAppInfo($app);
568
-		if (is_null($info)) {
569
-			return false;
570
-		}
571
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
572
-
573
-		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
574
-
575
-		$config = \OC::$server->getConfig();
576
-
577
-		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
578
-		if (array_key_exists('ocsid', $info)) {
579
-			$config->setAppValue($app, 'ocsid', $info['ocsid']);
580
-		}
581
-
582
-		//set remote/public handlers
583
-		foreach($info['remote'] as $name=>$path) {
584
-			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
585
-		}
586
-		foreach($info['public'] as $name=>$path) {
587
-			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
588
-		}
589
-
590
-		OC_App::setAppTypes($info['id']);
591
-
592
-		return $info['id'];
593
-	}
594
-
595
-	/**
596
-	 * @param string $script
597
-	 */
598
-	private static function includeAppScript($script) {
599
-		if ( file_exists($script) ){
600
-			include $script;
601
-		}
602
-	}
55
+    /** @var AppFetcher */
56
+    private $appFetcher;
57
+    /** @var IClientService */
58
+    private $clientService;
59
+    /** @var ITempManager */
60
+    private $tempManager;
61
+    /** @var ILogger */
62
+    private $logger;
63
+    /** @var IConfig */
64
+    private $config;
65
+    /** @var array - for caching the result of app fetcher */
66
+    private $apps = null;
67
+    /** @var bool|null - for caching the result of the ready status */
68
+    private $isInstanceReadyForUpdates = null;
69
+
70
+    /**
71
+     * @param AppFetcher $appFetcher
72
+     * @param IClientService $clientService
73
+     * @param ITempManager $tempManager
74
+     * @param ILogger $logger
75
+     * @param IConfig $config
76
+     */
77
+    public function __construct(AppFetcher $appFetcher,
78
+                                IClientService $clientService,
79
+                                ITempManager $tempManager,
80
+                                ILogger $logger,
81
+                                IConfig $config) {
82
+        $this->appFetcher = $appFetcher;
83
+        $this->clientService = $clientService;
84
+        $this->tempManager = $tempManager;
85
+        $this->logger = $logger;
86
+        $this->config = $config;
87
+    }
88
+
89
+    /**
90
+     * Installs an app that is located in one of the app folders already
91
+     *
92
+     * @param string $appId App to install
93
+     * @throws \Exception
94
+     * @return string app ID
95
+     */
96
+    public function installApp($appId) {
97
+        $app = \OC_App::findAppInDirectories($appId);
98
+        if($app === false) {
99
+            throw new \Exception('App not found in any app directory');
100
+        }
101
+
102
+        $basedir = $app['path'].'/'.$appId;
103
+        $info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
104
+
105
+        $l = \OC::$server->getL10N('core');
106
+
107
+        if(!is_array($info)) {
108
+            throw new \Exception(
109
+                $l->t('App "%s" cannot be installed because appinfo file cannot be read.',
110
+                    [$appId]
111
+                )
112
+            );
113
+        }
114
+
115
+        $version = implode('.', \OCP\Util::getVersion());
116
+        if (!\OC_App::isAppCompatible($version, $info)) {
117
+            throw new \Exception(
118
+                // TODO $l
119
+                $l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
120
+                    [$info['name']]
121
+                )
122
+            );
123
+        }
124
+
125
+        // check for required dependencies
126
+        \OC_App::checkAppDependencies($this->config, $l, $info);
127
+        \OC_App::registerAutoloading($appId, $basedir);
128
+
129
+        //install the database
130
+        if(is_file($basedir.'/appinfo/database.xml')) {
131
+            if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
132
+                OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
133
+            } else {
134
+                OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
135
+            }
136
+        } else {
137
+            $ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
138
+            $ms->migrate();
139
+        }
140
+
141
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
142
+
143
+        //run appinfo/install.php
144
+        self::includeAppScript($basedir . '/appinfo/install.php');
145
+
146
+        $appData = OC_App::getAppInfo($appId);
147
+        OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
148
+
149
+        //set the installed version
150
+        \OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
151
+        \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
152
+
153
+        //set remote/public handlers
154
+        foreach($info['remote'] as $name=>$path) {
155
+            \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
156
+        }
157
+        foreach($info['public'] as $name=>$path) {
158
+            \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
159
+        }
160
+
161
+        OC_App::setAppTypes($info['id']);
162
+
163
+        return $info['id'];
164
+    }
165
+
166
+    /**
167
+     * Updates the specified app from the appstore
168
+     *
169
+     * @param string $appId
170
+     * @return bool
171
+     */
172
+    public function updateAppstoreApp($appId) {
173
+        if($this->isUpdateAvailable($appId)) {
174
+            try {
175
+                $this->downloadApp($appId);
176
+            } catch (\Exception $e) {
177
+                $this->logger->logException($e, [
178
+                    'level' => ILogger::ERROR,
179
+                    'app' => 'core',
180
+                ]);
181
+                return false;
182
+            }
183
+            return OC_App::updateApp($appId);
184
+        }
185
+
186
+        return false;
187
+    }
188
+
189
+    /**
190
+     * Downloads an app and puts it into the app directory
191
+     *
192
+     * @param string $appId
193
+     *
194
+     * @throws \Exception If the installation was not successful
195
+     */
196
+    public function downloadApp($appId) {
197
+        $appId = strtolower($appId);
198
+
199
+        $apps = $this->appFetcher->get();
200
+        foreach($apps as $app) {
201
+            if($app['id'] === $appId) {
202
+                // Load the certificate
203
+                $certificate = new X509();
204
+                $certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
205
+                $loadedCertificate = $certificate->loadX509($app['certificate']);
206
+
207
+                // Verify if the certificate has been revoked
208
+                $crl = new X509();
209
+                $crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
210
+                $crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
211
+                if($crl->validateSignature() !== true) {
212
+                    throw new \Exception('Could not validate CRL signature');
213
+                }
214
+                $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
215
+                $revoked = $crl->getRevoked($csn);
216
+                if ($revoked !== false) {
217
+                    throw new \Exception(
218
+                        sprintf(
219
+                            'Certificate "%s" has been revoked',
220
+                            $csn
221
+                        )
222
+                    );
223
+                }
224
+
225
+                // Verify if the certificate has been issued by the Nextcloud Code Authority CA
226
+                if($certificate->validateSignature() !== true) {
227
+                    throw new \Exception(
228
+                        sprintf(
229
+                            'App with id %s has a certificate not issued by a trusted Code Signing Authority',
230
+                            $appId
231
+                        )
232
+                    );
233
+                }
234
+
235
+                // Verify if the certificate is issued for the requested app id
236
+                $certInfo = openssl_x509_parse($app['certificate']);
237
+                if(!isset($certInfo['subject']['CN'])) {
238
+                    throw new \Exception(
239
+                        sprintf(
240
+                            'App with id %s has a cert with no CN',
241
+                            $appId
242
+                        )
243
+                    );
244
+                }
245
+                if($certInfo['subject']['CN'] !== $appId) {
246
+                    throw new \Exception(
247
+                        sprintf(
248
+                            'App with id %s has a cert issued to %s',
249
+                            $appId,
250
+                            $certInfo['subject']['CN']
251
+                        )
252
+                    );
253
+                }
254
+
255
+                // Download the release
256
+                $tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
257
+                $client = $this->clientService->newClient();
258
+                $client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
259
+
260
+                // Check if the signature actually matches the downloaded content
261
+                $certificate = openssl_get_publickey($app['certificate']);
262
+                $verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
263
+                openssl_free_key($certificate);
264
+
265
+                if($verified === true) {
266
+                    // Seems to match, let's proceed
267
+                    $extractDir = $this->tempManager->getTemporaryFolder();
268
+                    $archive = new TAR($tempFile);
269
+
270
+                    if($archive) {
271
+                        if (!$archive->extract($extractDir)) {
272
+                            throw new \Exception(
273
+                                sprintf(
274
+                                    'Could not extract app %s',
275
+                                    $appId
276
+                                )
277
+                            );
278
+                        }
279
+                        $allFiles = scandir($extractDir);
280
+                        $folders = array_diff($allFiles, ['.', '..']);
281
+                        $folders = array_values($folders);
282
+
283
+                        if(count($folders) > 1) {
284
+                            throw new \Exception(
285
+                                sprintf(
286
+                                    'Extracted app %s has more than 1 folder',
287
+                                    $appId
288
+                                )
289
+                            );
290
+                        }
291
+
292
+                        // Check if appinfo/info.xml has the same app ID as well
293
+                        $loadEntities = libxml_disable_entity_loader(false);
294
+                        $xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
295
+                        libxml_disable_entity_loader($loadEntities);
296
+                        if((string)$xml->id !== $appId) {
297
+                            throw new \Exception(
298
+                                sprintf(
299
+                                    'App for id %s has a wrong app ID in info.xml: %s',
300
+                                    $appId,
301
+                                    (string)$xml->id
302
+                                )
303
+                            );
304
+                        }
305
+
306
+                        // Check if the version is lower than before
307
+                        $currentVersion = OC_App::getAppVersion($appId);
308
+                        $newVersion = (string)$xml->version;
309
+                        if(version_compare($currentVersion, $newVersion) === 1) {
310
+                            throw new \Exception(
311
+                                sprintf(
312
+                                    'App for id %s has version %s and tried to update to lower version %s',
313
+                                    $appId,
314
+                                    $currentVersion,
315
+                                    $newVersion
316
+                                )
317
+                            );
318
+                        }
319
+
320
+                        $baseDir = OC_App::getInstallPath() . '/' . $appId;
321
+                        // Remove old app with the ID if existent
322
+                        OC_Helper::rmdirr($baseDir);
323
+                        // Move to app folder
324
+                        if(@mkdir($baseDir)) {
325
+                            $extractDir .= '/' . $folders[0];
326
+                            OC_Helper::copyr($extractDir, $baseDir);
327
+                        }
328
+                        OC_Helper::copyr($extractDir, $baseDir);
329
+                        OC_Helper::rmdirr($extractDir);
330
+                        return;
331
+                    } else {
332
+                        throw new \Exception(
333
+                            sprintf(
334
+                                'Could not extract app with ID %s to %s',
335
+                                $appId,
336
+                                $extractDir
337
+                            )
338
+                        );
339
+                    }
340
+                } else {
341
+                    // Signature does not match
342
+                    throw new \Exception(
343
+                        sprintf(
344
+                            'App with id %s has invalid signature',
345
+                            $appId
346
+                        )
347
+                    );
348
+                }
349
+            }
350
+        }
351
+
352
+        throw new \Exception(
353
+            sprintf(
354
+                'Could not download app %s',
355
+                $appId
356
+            )
357
+        );
358
+    }
359
+
360
+    /**
361
+     * Check if an update for the app is available
362
+     *
363
+     * @param string $appId
364
+     * @return string|false false or the version number of the update
365
+     */
366
+    public function isUpdateAvailable($appId) {
367
+        if ($this->isInstanceReadyForUpdates === null) {
368
+            $installPath = OC_App::getInstallPath();
369
+            if ($installPath === false || $installPath === null) {
370
+                $this->isInstanceReadyForUpdates = false;
371
+            } else {
372
+                $this->isInstanceReadyForUpdates = true;
373
+            }
374
+        }
375
+
376
+        if ($this->isInstanceReadyForUpdates === false) {
377
+            return false;
378
+        }
379
+
380
+        if ($this->isInstalledFromGit($appId) === true) {
381
+            return false;
382
+        }
383
+
384
+        if ($this->apps === null) {
385
+            $this->apps = $this->appFetcher->get();
386
+        }
387
+
388
+        foreach($this->apps as $app) {
389
+            if($app['id'] === $appId) {
390
+                $currentVersion = OC_App::getAppVersion($appId);
391
+                $newestVersion = $app['releases'][0]['version'];
392
+                if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
393
+                    return $newestVersion;
394
+                } else {
395
+                    return false;
396
+                }
397
+            }
398
+        }
399
+
400
+        return false;
401
+    }
402
+
403
+    /**
404
+     * Check if app has been installed from git
405
+     * @param string $name name of the application to remove
406
+     * @return boolean
407
+     *
408
+     * The function will check if the path contains a .git folder
409
+     */
410
+    private function isInstalledFromGit($appId) {
411
+        $app = \OC_App::findAppInDirectories($appId);
412
+        if($app === false) {
413
+            return false;
414
+        }
415
+        $basedir = $app['path'].'/'.$appId;
416
+        return file_exists($basedir.'/.git/');
417
+    }
418
+
419
+    /**
420
+     * Check if app is already downloaded
421
+     * @param string $name name of the application to remove
422
+     * @return boolean
423
+     *
424
+     * The function will check if the app is already downloaded in the apps repository
425
+     */
426
+    public function isDownloaded($name) {
427
+        foreach(\OC::$APPSROOTS as $dir) {
428
+            $dirToTest  = $dir['path'];
429
+            $dirToTest .= '/';
430
+            $dirToTest .= $name;
431
+            $dirToTest .= '/';
432
+
433
+            if (is_dir($dirToTest)) {
434
+                return true;
435
+            }
436
+        }
437
+
438
+        return false;
439
+    }
440
+
441
+    /**
442
+     * Removes an app
443
+     * @param string $appId ID of the application to remove
444
+     * @return boolean
445
+     *
446
+     *
447
+     * This function works as follows
448
+     *   -# call uninstall repair steps
449
+     *   -# removing the files
450
+     *
451
+     * The function will not delete preferences, tables and the configuration,
452
+     * this has to be done by the function oc_app_uninstall().
453
+     */
454
+    public function removeApp($appId) {
455
+        if($this->isDownloaded( $appId )) {
456
+            if (\OC::$server->getAppManager()->isShipped($appId)) {
457
+                return false;
458
+            }
459
+            $appDir = OC_App::getInstallPath() . '/' . $appId;
460
+            OC_Helper::rmdirr($appDir);
461
+            return true;
462
+        }else{
463
+            \OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR);
464
+
465
+            return false;
466
+        }
467
+
468
+    }
469
+
470
+    /**
471
+     * Installs the app within the bundle and marks the bundle as installed
472
+     *
473
+     * @param Bundle $bundle
474
+     * @throws \Exception If app could not get installed
475
+     */
476
+    public function installAppBundle(Bundle $bundle) {
477
+        $appIds = $bundle->getAppIdentifiers();
478
+        foreach($appIds as $appId) {
479
+            if(!$this->isDownloaded($appId)) {
480
+                $this->downloadApp($appId);
481
+            }
482
+            $this->installApp($appId);
483
+            $app = new OC_App();
484
+            $app->enable($appId);
485
+        }
486
+        $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
487
+        $bundles[] = $bundle->getIdentifier();
488
+        $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
489
+    }
490
+
491
+    /**
492
+     * Installs shipped apps
493
+     *
494
+     * This function installs all apps found in the 'apps' directory that should be enabled by default;
495
+     * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
496
+     *                         working ownCloud at the end instead of an aborted update.
497
+     * @return array Array of error messages (appid => Exception)
498
+     */
499
+    public static function installShippedApps($softErrors = false) {
500
+        $appManager = \OC::$server->getAppManager();
501
+        $config = \OC::$server->getConfig();
502
+        $errors = [];
503
+        foreach(\OC::$APPSROOTS as $app_dir) {
504
+            if($dir = opendir( $app_dir['path'] )) {
505
+                while( false !== ( $filename = readdir( $dir ))) {
506
+                    if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
507
+                        if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
508
+                            if($config->getAppValue($filename, "installed_version", null) === null) {
509
+                                $info=OC_App::getAppInfo($filename);
510
+                                $enabled = isset($info['default_enable']);
511
+                                if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
512
+                                      && $config->getAppValue($filename, 'enabled') !== 'no') {
513
+                                    if ($softErrors) {
514
+                                        try {
515
+                                            Installer::installShippedApp($filename);
516
+                                        } catch (HintException $e) {
517
+                                            if ($e->getPrevious() instanceof TableExistsException) {
518
+                                                $errors[$filename] = $e;
519
+                                                continue;
520
+                                            }
521
+                                            throw $e;
522
+                                        }
523
+                                    } else {
524
+                                        Installer::installShippedApp($filename);
525
+                                    }
526
+                                    $config->setAppValue($filename, 'enabled', 'yes');
527
+                                }
528
+                            }
529
+                        }
530
+                    }
531
+                }
532
+                closedir( $dir );
533
+            }
534
+        }
535
+
536
+        return $errors;
537
+    }
538
+
539
+    /**
540
+     * install an app already placed in the app folder
541
+     * @param string $app id of the app to install
542
+     * @return integer
543
+     */
544
+    public static function installShippedApp($app) {
545
+        //install the database
546
+        $appPath = OC_App::getAppPath($app);
547
+        \OC_App::registerAutoloading($app, $appPath);
548
+
549
+        if(is_file("$appPath/appinfo/database.xml")) {
550
+            try {
551
+                OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
552
+            } catch (TableExistsException $e) {
553
+                throw new HintException(
554
+                    'Failed to enable app ' . $app,
555
+                    'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
556
+                    0, $e
557
+                );
558
+            }
559
+        } else {
560
+            $ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
561
+            $ms->migrate();
562
+        }
563
+
564
+        //run appinfo/install.php
565
+        self::includeAppScript("$appPath/appinfo/install.php");
566
+
567
+        $info = OC_App::getAppInfo($app);
568
+        if (is_null($info)) {
569
+            return false;
570
+        }
571
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
572
+
573
+        OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
574
+
575
+        $config = \OC::$server->getConfig();
576
+
577
+        $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
578
+        if (array_key_exists('ocsid', $info)) {
579
+            $config->setAppValue($app, 'ocsid', $info['ocsid']);
580
+        }
581
+
582
+        //set remote/public handlers
583
+        foreach($info['remote'] as $name=>$path) {
584
+            $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
585
+        }
586
+        foreach($info['public'] as $name=>$path) {
587
+            $config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
588
+        }
589
+
590
+        OC_App::setAppTypes($info['id']);
591
+
592
+        return $info['id'];
593
+    }
594
+
595
+    /**
596
+     * @param string $script
597
+     */
598
+    private static function includeAppScript($script) {
599
+        if ( file_exists($script) ){
600
+            include $script;
601
+        }
602
+    }
603 603
 }
Please login to merge, or discard this patch.
Spacing   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -95,7 +95,7 @@  discard block
 block discarded – undo
95 95
 	 */
96 96
 	public function installApp($appId) {
97 97
 		$app = \OC_App::findAppInDirectories($appId);
98
-		if($app === false) {
98
+		if ($app === false) {
99 99
 			throw new \Exception('App not found in any app directory');
100 100
 		}
101 101
 
@@ -104,7 +104,7 @@  discard block
 block discarded – undo
104 104
 
105 105
 		$l = \OC::$server->getL10N('core');
106 106
 
107
-		if(!is_array($info)) {
107
+		if (!is_array($info)) {
108 108
 			throw new \Exception(
109 109
 				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
110 110
 					[$appId]
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
 		\OC_App::registerAutoloading($appId, $basedir);
128 128
 
129 129
 		//install the database
130
-		if(is_file($basedir.'/appinfo/database.xml')) {
130
+		if (is_file($basedir.'/appinfo/database.xml')) {
131 131
 			if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
132 132
 				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
133 133
 			} else {
@@ -141,7 +141,7 @@  discard block
 block discarded – undo
141 141
 		\OC_App::setupBackgroundJobs($info['background-jobs']);
142 142
 
143 143
 		//run appinfo/install.php
144
-		self::includeAppScript($basedir . '/appinfo/install.php');
144
+		self::includeAppScript($basedir.'/appinfo/install.php');
145 145
 
146 146
 		$appData = OC_App::getAppInfo($appId);
147 147
 		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
@@ -151,10 +151,10 @@  discard block
 block discarded – undo
151 151
 		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
152 152
 
153 153
 		//set remote/public handlers
154
-		foreach($info['remote'] as $name=>$path) {
154
+		foreach ($info['remote'] as $name=>$path) {
155 155
 			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
156 156
 		}
157
-		foreach($info['public'] as $name=>$path) {
157
+		foreach ($info['public'] as $name=>$path) {
158 158
 			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
159 159
 		}
160 160
 
@@ -170,7 +170,7 @@  discard block
 block discarded – undo
170 170
 	 * @return bool
171 171
 	 */
172 172
 	public function updateAppstoreApp($appId) {
173
-		if($this->isUpdateAvailable($appId)) {
173
+		if ($this->isUpdateAvailable($appId)) {
174 174
 			try {
175 175
 				$this->downloadApp($appId);
176 176
 			} catch (\Exception $e) {
@@ -197,18 +197,18 @@  discard block
 block discarded – undo
197 197
 		$appId = strtolower($appId);
198 198
 
199 199
 		$apps = $this->appFetcher->get();
200
-		foreach($apps as $app) {
201
-			if($app['id'] === $appId) {
200
+		foreach ($apps as $app) {
201
+			if ($app['id'] === $appId) {
202 202
 				// Load the certificate
203 203
 				$certificate = new X509();
204
-				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
204
+				$certificate->loadCA(file_get_contents(__DIR__.'/../../resources/codesigning/root.crt'));
205 205
 				$loadedCertificate = $certificate->loadX509($app['certificate']);
206 206
 
207 207
 				// Verify if the certificate has been revoked
208 208
 				$crl = new X509();
209
-				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
210
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
211
-				if($crl->validateSignature() !== true) {
209
+				$crl->loadCA(file_get_contents(__DIR__.'/../../resources/codesigning/root.crt'));
210
+				$crl->loadCRL(file_get_contents(__DIR__.'/../../resources/codesigning/root.crl'));
211
+				if ($crl->validateSignature() !== true) {
212 212
 					throw new \Exception('Could not validate CRL signature');
213 213
 				}
214 214
 				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
@@ -223,7 +223,7 @@  discard block
 block discarded – undo
223 223
 				}
224 224
 
225 225
 				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
226
-				if($certificate->validateSignature() !== true) {
226
+				if ($certificate->validateSignature() !== true) {
227 227
 					throw new \Exception(
228 228
 						sprintf(
229 229
 							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
@@ -234,7 +234,7 @@  discard block
 block discarded – undo
234 234
 
235 235
 				// Verify if the certificate is issued for the requested app id
236 236
 				$certInfo = openssl_x509_parse($app['certificate']);
237
-				if(!isset($certInfo['subject']['CN'])) {
237
+				if (!isset($certInfo['subject']['CN'])) {
238 238
 					throw new \Exception(
239 239
 						sprintf(
240 240
 							'App with id %s has a cert with no CN',
@@ -242,7 +242,7 @@  discard block
 block discarded – undo
242 242
 						)
243 243
 					);
244 244
 				}
245
-				if($certInfo['subject']['CN'] !== $appId) {
245
+				if ($certInfo['subject']['CN'] !== $appId) {
246 246
 					throw new \Exception(
247 247
 						sprintf(
248 248
 							'App with id %s has a cert issued to %s',
@@ -259,15 +259,15 @@  discard block
 block discarded – undo
259 259
 
260 260
 				// Check if the signature actually matches the downloaded content
261 261
 				$certificate = openssl_get_publickey($app['certificate']);
262
-				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
262
+				$verified = (bool) openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
263 263
 				openssl_free_key($certificate);
264 264
 
265
-				if($verified === true) {
265
+				if ($verified === true) {
266 266
 					// Seems to match, let's proceed
267 267
 					$extractDir = $this->tempManager->getTemporaryFolder();
268 268
 					$archive = new TAR($tempFile);
269 269
 
270
-					if($archive) {
270
+					if ($archive) {
271 271
 						if (!$archive->extract($extractDir)) {
272 272
 							throw new \Exception(
273 273
 								sprintf(
@@ -280,7 +280,7 @@  discard block
 block discarded – undo
280 280
 						$folders = array_diff($allFiles, ['.', '..']);
281 281
 						$folders = array_values($folders);
282 282
 
283
-						if(count($folders) > 1) {
283
+						if (count($folders) > 1) {
284 284
 							throw new \Exception(
285 285
 								sprintf(
286 286
 									'Extracted app %s has more than 1 folder',
@@ -291,22 +291,22 @@  discard block
 block discarded – undo
291 291
 
292 292
 						// Check if appinfo/info.xml has the same app ID as well
293 293
 						$loadEntities = libxml_disable_entity_loader(false);
294
-						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
294
+						$xml = simplexml_load_file($extractDir.'/'.$folders[0].'/appinfo/info.xml');
295 295
 						libxml_disable_entity_loader($loadEntities);
296
-						if((string)$xml->id !== $appId) {
296
+						if ((string) $xml->id !== $appId) {
297 297
 							throw new \Exception(
298 298
 								sprintf(
299 299
 									'App for id %s has a wrong app ID in info.xml: %s',
300 300
 									$appId,
301
-									(string)$xml->id
301
+									(string) $xml->id
302 302
 								)
303 303
 							);
304 304
 						}
305 305
 
306 306
 						// Check if the version is lower than before
307 307
 						$currentVersion = OC_App::getAppVersion($appId);
308
-						$newVersion = (string)$xml->version;
309
-						if(version_compare($currentVersion, $newVersion) === 1) {
308
+						$newVersion = (string) $xml->version;
309
+						if (version_compare($currentVersion, $newVersion) === 1) {
310 310
 							throw new \Exception(
311 311
 								sprintf(
312 312
 									'App for id %s has version %s and tried to update to lower version %s',
@@ -317,12 +317,12 @@  discard block
 block discarded – undo
317 317
 							);
318 318
 						}
319 319
 
320
-						$baseDir = OC_App::getInstallPath() . '/' . $appId;
320
+						$baseDir = OC_App::getInstallPath().'/'.$appId;
321 321
 						// Remove old app with the ID if existent
322 322
 						OC_Helper::rmdirr($baseDir);
323 323
 						// Move to app folder
324
-						if(@mkdir($baseDir)) {
325
-							$extractDir .= '/' . $folders[0];
324
+						if (@mkdir($baseDir)) {
325
+							$extractDir .= '/'.$folders[0];
326 326
 							OC_Helper::copyr($extractDir, $baseDir);
327 327
 						}
328 328
 						OC_Helper::copyr($extractDir, $baseDir);
@@ -385,8 +385,8 @@  discard block
 block discarded – undo
385 385
 			$this->apps = $this->appFetcher->get();
386 386
 		}
387 387
 
388
-		foreach($this->apps as $app) {
389
-			if($app['id'] === $appId) {
388
+		foreach ($this->apps as $app) {
389
+			if ($app['id'] === $appId) {
390 390
 				$currentVersion = OC_App::getAppVersion($appId);
391 391
 				$newestVersion = $app['releases'][0]['version'];
392 392
 				if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
@@ -409,7 +409,7 @@  discard block
 block discarded – undo
409 409
 	 */
410 410
 	private function isInstalledFromGit($appId) {
411 411
 		$app = \OC_App::findAppInDirectories($appId);
412
-		if($app === false) {
412
+		if ($app === false) {
413 413
 			return false;
414 414
 		}
415 415
 		$basedir = $app['path'].'/'.$appId;
@@ -424,7 +424,7 @@  discard block
 block discarded – undo
424 424
 	 * The function will check if the app is already downloaded in the apps repository
425 425
 	 */
426 426
 	public function isDownloaded($name) {
427
-		foreach(\OC::$APPSROOTS as $dir) {
427
+		foreach (\OC::$APPSROOTS as $dir) {
428 428
 			$dirToTest  = $dir['path'];
429 429
 			$dirToTest .= '/';
430 430
 			$dirToTest .= $name;
@@ -452,14 +452,14 @@  discard block
 block discarded – undo
452 452
 	 * this has to be done by the function oc_app_uninstall().
453 453
 	 */
454 454
 	public function removeApp($appId) {
455
-		if($this->isDownloaded( $appId )) {
455
+		if ($this->isDownloaded($appId)) {
456 456
 			if (\OC::$server->getAppManager()->isShipped($appId)) {
457 457
 				return false;
458 458
 			}
459
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
459
+			$appDir = OC_App::getInstallPath().'/'.$appId;
460 460
 			OC_Helper::rmdirr($appDir);
461 461
 			return true;
462
-		}else{
462
+		} else {
463 463
 			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR);
464 464
 
465 465
 			return false;
@@ -475,8 +475,8 @@  discard block
 block discarded – undo
475 475
 	 */
476 476
 	public function installAppBundle(Bundle $bundle) {
477 477
 		$appIds = $bundle->getAppIdentifiers();
478
-		foreach($appIds as $appId) {
479
-			if(!$this->isDownloaded($appId)) {
478
+		foreach ($appIds as $appId) {
479
+			if (!$this->isDownloaded($appId)) {
480 480
 				$this->downloadApp($appId);
481 481
 			}
482 482
 			$this->installApp($appId);
@@ -500,13 +500,13 @@  discard block
 block discarded – undo
500 500
 		$appManager = \OC::$server->getAppManager();
501 501
 		$config = \OC::$server->getConfig();
502 502
 		$errors = [];
503
-		foreach(\OC::$APPSROOTS as $app_dir) {
504
-			if($dir = opendir( $app_dir['path'] )) {
505
-				while( false !== ( $filename = readdir( $dir ))) {
506
-					if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
507
-						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
508
-							if($config->getAppValue($filename, "installed_version", null) === null) {
509
-								$info=OC_App::getAppInfo($filename);
503
+		foreach (\OC::$APPSROOTS as $app_dir) {
504
+			if ($dir = opendir($app_dir['path'])) {
505
+				while (false !== ($filename = readdir($dir))) {
506
+					if ($filename[0] !== '.' and is_dir($app_dir['path']."/$filename")) {
507
+						if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) {
508
+							if ($config->getAppValue($filename, "installed_version", null) === null) {
509
+								$info = OC_App::getAppInfo($filename);
510 510
 								$enabled = isset($info['default_enable']);
511 511
 								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
512 512
 									  && $config->getAppValue($filename, 'enabled') !== 'no') {
@@ -529,7 +529,7 @@  discard block
 block discarded – undo
529 529
 						}
530 530
 					}
531 531
 				}
532
-				closedir( $dir );
532
+				closedir($dir);
533 533
 			}
534 534
 		}
535 535
 
@@ -546,12 +546,12 @@  discard block
 block discarded – undo
546 546
 		$appPath = OC_App::getAppPath($app);
547 547
 		\OC_App::registerAutoloading($app, $appPath);
548 548
 
549
-		if(is_file("$appPath/appinfo/database.xml")) {
549
+		if (is_file("$appPath/appinfo/database.xml")) {
550 550
 			try {
551 551
 				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
552 552
 			} catch (TableExistsException $e) {
553 553
 				throw new HintException(
554
-					'Failed to enable app ' . $app,
554
+					'Failed to enable app '.$app,
555 555
 					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
556 556
 					0, $e
557 557
 				);
@@ -580,10 +580,10 @@  discard block
 block discarded – undo
580 580
 		}
581 581
 
582 582
 		//set remote/public handlers
583
-		foreach($info['remote'] as $name=>$path) {
583
+		foreach ($info['remote'] as $name=>$path) {
584 584
 			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
585 585
 		}
586
-		foreach($info['public'] as $name=>$path) {
586
+		foreach ($info['public'] as $name=>$path) {
587 587
 			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
588 588
 		}
589 589
 
@@ -596,7 +596,7 @@  discard block
 block discarded – undo
596 596
 	 * @param string $script
597 597
 	 */
598 598
 	private static function includeAppScript($script) {
599
-		if ( file_exists($script) ){
599
+		if (file_exists($script)) {
600 600
 			include $script;
601 601
 		}
602 602
 	}
Please login to merge, or discard this patch.
settings/Controller/AppSettingsController.php 2 patches
Indentation   +484 added lines, -484 removed lines patch added patch discarded remove patch
@@ -58,489 +58,489 @@
 block discarded – undo
58 58
  */
59 59
 class AppSettingsController extends Controller {
60 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
-		\OC_Util::addVendorScript('core', 'marked/marked.min');
137
-		$params = [];
138
-		$params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
139
-		$params['updateCount'] = count($this->getAppsWithUpdates());
140
-		$params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
141
-		$params['bundles'] = $this->getBundles();
142
-		$this->navigationManager->setActiveEntry('core_apps');
143
-
144
-		$templateResponse = new TemplateResponse('settings', 'settings-vue', ['serverData' => $params]);
145
-		$policy = new ContentSecurityPolicy();
146
-		$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
147
-		$templateResponse->setContentSecurityPolicy($policy);
148
-
149
-		return $templateResponse;
150
-	}
151
-
152
-	private function getAppsWithUpdates() {
153
-		$appClass = new \OC_App();
154
-		$apps = $appClass->listAllApps();
155
-		foreach($apps as $key => $app) {
156
-			$newVersion = $this->installer->isUpdateAvailable($app['id']);
157
-			if($newVersion === false) {
158
-				unset($apps[$key]);
159
-			}
160
-		}
161
-		return $apps;
162
-	}
163
-
164
-	private function getBundles() {
165
-		$result = [];
166
-		$bundles = $this->bundleFetcher->getBundles();
167
-		foreach ($bundles as $bundle) {
168
-			$result[] = [
169
-				'name' => $bundle->getName(),
170
-				'id' => $bundle->getIdentifier(),
171
-				'appIdentifiers' => $bundle->getAppIdentifiers()
172
-			];
173
-		}
174
-		return $result;
175
-
176
-	}
177
-
178
-	/**
179
-	 * Get all available categories
180
-	 *
181
-	 * @return JSONResponse
182
-	 */
183
-	public function listCategories(): JSONResponse {
184
-		return new JSONResponse($this->getAllCategories());
185
-	}
186
-
187
-	private function getAllCategories() {
188
-		$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
189
-
190
-		$formattedCategories = [];
191
-		$categories = $this->categoryFetcher->get();
192
-		foreach($categories as $category) {
193
-			$formattedCategories[] = [
194
-				'id' => $category['id'],
195
-				'ident' => $category['id'],
196
-				'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
197
-			];
198
-		}
199
-
200
-		return $formattedCategories;
201
-	}
202
-
203
-	private function fetchApps() {
204
-		$appClass = new \OC_App();
205
-		$apps = $appClass->listAllApps();
206
-		foreach ($apps as $app) {
207
-			$app['installed'] = true;
208
-			$this->allApps[$app['id']] = $app;
209
-		}
210
-
211
-		$apps = $this->getAppsForCategory('');
212
-		foreach ($apps as $app) {
213
-			$app['appstore'] = true;
214
-			if (!array_key_exists($app['id'], $this->allApps)) {
215
-				$this->allApps[$app['id']] = $app;
216
-			} else {
217
-				$this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
218
-			}
219
-		}
220
-
221
-		// add bundle information
222
-		$bundles = $this->bundleFetcher->getBundles();
223
-		foreach($bundles as $bundle) {
224
-			foreach($bundle->getAppIdentifiers() as $identifier) {
225
-				foreach($this->allApps as &$app) {
226
-					if($app['id'] === $identifier) {
227
-						$app['bundleId'] = $bundle->getIdentifier();
228
-						continue;
229
-					}
230
-				}
231
-			}
232
-		}
233
-	}
234
-
235
-	private function getAllApps() {
236
-		return $this->allApps;
237
-	}
238
-	/**
239
-	 * Get all available apps in a category
240
-	 *
241
-	 * @param string $category
242
-	 * @return JSONResponse
243
-	 * @throws \Exception
244
-	 */
245
-	public function listApps(): JSONResponse {
246
-
247
-		$this->fetchApps();
248
-		$apps = $this->getAllApps();
249
-
250
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
251
-
252
-		// Extend existing app details
253
-		$apps = array_map(function($appData) use ($dependencyAnalyzer) {
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
-			$newVersion = $this->installer->isUpdateAvailable($appData['id']);
259
-			if($newVersion) {
260
-				$appData['update'] = $newVersion;
261
-			}
262
-
263
-			// fix groups to be an array
264
-			$groups = array();
265
-			if (is_string($appData['groups'])) {
266
-				$groups = json_decode($appData['groups']);
267
-			}
268
-			$appData['groups'] = $groups;
269
-			$appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
270
-
271
-			// fix licence vs license
272
-			if (isset($appData['license']) && !isset($appData['licence'])) {
273
-				$appData['licence'] = $appData['license'];
274
-			}
275
-
276
-			// analyse dependencies
277
-			$missing = $dependencyAnalyzer->analyze($appData);
278
-			$appData['canInstall'] = empty($missing);
279
-			$appData['missingDependencies'] = $missing;
280
-
281
-			$appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
282
-			$appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
283
-
284
-			return $appData;
285
-		}, $apps);
286
-
287
-		usort($apps, [$this, 'sortApps']);
288
-
289
-		return new JSONResponse(['apps' => $apps, 'status' => 'success']);
290
-	}
291
-
292
-	/**
293
-	 * Get all apps for a category from the app store
294
-	 *
295
-	 * @param string $requestedCategory
296
-	 * @return array
297
-	 * @throws \Exception
298
-	 */
299
-	private function getAppsForCategory($requestedCategory = ''): array {
300
-		$versionParser = new VersionParser();
301
-		$formattedApps = [];
302
-		$apps = $this->appFetcher->get();
303
-		foreach($apps as $app) {
304
-			// Skip all apps not in the requested category
305
-			if ($requestedCategory !== '') {
306
-				$isInCategory = false;
307
-				foreach($app['categories'] as $category) {
308
-					if($category === $requestedCategory) {
309
-						$isInCategory = true;
310
-					}
311
-				}
312
-				if(!$isInCategory) {
313
-					continue;
314
-				}
315
-			}
316
-
317
-			$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
318
-			$nextCloudVersionDependencies = [];
319
-			if($nextCloudVersion->getMinimumVersion() !== '') {
320
-				$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
321
-			}
322
-			if($nextCloudVersion->getMaximumVersion() !== '') {
323
-				$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
324
-			}
325
-			$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
326
-			$existsLocally = \OC_App::getAppPath($app['id']) !== false;
327
-			$phpDependencies = [];
328
-			if($phpVersion->getMinimumVersion() !== '') {
329
-				$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
330
-			}
331
-			if($phpVersion->getMaximumVersion() !== '') {
332
-				$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
333
-			}
334
-			if(isset($app['releases'][0]['minIntSize'])) {
335
-				$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
336
-			}
337
-			$authors = '';
338
-			foreach($app['authors'] as $key => $author) {
339
-				$authors .= $author['name'];
340
-				if($key !== count($app['authors']) - 1) {
341
-					$authors .= ', ';
342
-				}
343
-			}
344
-
345
-			$currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
346
-			$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
347
-			$groups = null;
348
-			if($enabledValue !== 'no' && $enabledValue !== 'yes') {
349
-				$groups = $enabledValue;
350
-			}
351
-
352
-			$currentVersion = '';
353
-			if($this->appManager->isInstalled($app['id'])) {
354
-				$currentVersion = $this->appManager->getAppVersion($app['id']);
355
-			} else {
356
-				$currentLanguage = $app['releases'][0]['version'];
357
-			}
358
-
359
-			$formattedApps[] = [
360
-				'id' => $app['id'],
361
-				'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
362
-				'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
363
-				'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
364
-				'license' => $app['releases'][0]['licenses'],
365
-				'author' => $authors,
366
-				'shipped' => false,
367
-				'version' => $currentVersion,
368
-				'default_enable' => '',
369
-				'types' => [],
370
-				'documentation' => [
371
-					'admin' => $app['adminDocs'],
372
-					'user' => $app['userDocs'],
373
-					'developer' => $app['developerDocs']
374
-				],
375
-				'website' => $app['website'],
376
-				'bugs' => $app['issueTracker'],
377
-				'detailpage' => $app['website'],
378
-				'dependencies' => array_merge(
379
-					$nextCloudVersionDependencies,
380
-					$phpDependencies
381
-				),
382
-				'level' => ($app['isFeatured'] === true) ? 200 : 100,
383
-				'missingMaxOwnCloudVersion' => false,
384
-				'missingMinOwnCloudVersion' => false,
385
-				'canInstall' => true,
386
-				'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
387
-				'score' => $app['ratingOverall'],
388
-				'ratingNumOverall' => $app['ratingNumOverall'],
389
-				'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
390
-				'removable' => $existsLocally,
391
-				'active' => $this->appManager->isEnabledForUser($app['id']),
392
-				'needsDownload' => !$existsLocally,
393
-				'groups' => $groups,
394
-				'fromAppStore' => true,
395
-				'appstoreData' => $app,
396
-			];
397
-		}
398
-
399
-		return $formattedApps;
400
-	}
401
-
402
-	/**
403
-	 * @PasswordConfirmationRequired
404
-	 *
405
-	 * @param string $appId
406
-	 * @param array $groups
407
-	 * @return JSONResponse
408
-	 */
409
-	public function enableApp(string $appId, array $groups = []): JSONResponse {
410
-		return $this->enableApps([$appId], $groups);
411
-	}
412
-
413
-	/**
414
-	 * Enable one or more apps
415
-	 *
416
-	 * apps will be enabled for specific groups only if $groups is defined
417
-	 *
418
-	 * @PasswordConfirmationRequired
419
-	 * @param array $appIds
420
-	 * @param array $groups
421
-	 * @return JSONResponse
422
-	 */
423
-	public function enableApps(array $appIds, array $groups = []): JSONResponse {
424
-		try {
425
-			$updateRequired = false;
426
-
427
-			foreach ($appIds as $appId) {
428
-				$appId = OC_App::cleanAppId($appId);
429
-
430
-				// Check if app is already downloaded
431
-				/** @var Installer $installer */
432
-				$installer = \OC::$server->query(Installer::class);
433
-				$isDownloaded = $installer->isDownloaded($appId);
434
-
435
-				if(!$isDownloaded) {
436
-					$installer->downloadApp($appId);
437
-				}
438
-
439
-				$installer->installApp($appId);
440
-
441
-				if (count($groups) > 0) {
442
-					$this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
443
-				} else {
444
-					$this->appManager->enableApp($appId);
445
-				}
446
-				if (\OC_App::shouldUpgrade($appId)) {
447
-					$updateRequired = true;
448
-				}
449
-			}
450
-			return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
451
-
452
-		} catch (\Exception $e) {
453
-			$this->logger->logException($e);
454
-			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
455
-		}
456
-	}
457
-
458
-	private function getGroupList(array $groups) {
459
-		$groupManager = \OC::$server->getGroupManager();
460
-		$groupsList = [];
461
-		foreach ($groups as $group) {
462
-			$groupItem = $groupManager->get($group);
463
-			if ($groupItem instanceof \OCP\IGroup) {
464
-				$groupsList[] = $groupManager->get($group);
465
-			}
466
-		}
467
-		return $groupsList;
468
-	}
469
-
470
-	/**
471
-	 * @PasswordConfirmationRequired
472
-	 *
473
-	 * @param string $appId
474
-	 * @return JSONResponse
475
-	 */
476
-	public function disableApp(string $appId): JSONResponse {
477
-		return $this->disableApps([$appId]);
478
-	}
479
-
480
-	/**
481
-	 * @PasswordConfirmationRequired
482
-	 *
483
-	 * @param array $appIds
484
-	 * @return JSONResponse
485
-	 */
486
-	public function disableApps(array $appIds): JSONResponse {
487
-		try {
488
-			foreach ($appIds as $appId) {
489
-				$appId = OC_App::cleanAppId($appId);
490
-				$this->appManager->disableApp($appId);
491
-			}
492
-			return new JSONResponse([]);
493
-		} catch (\Exception $e) {
494
-			$this->logger->logException($e);
495
-			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
496
-		}
497
-	}
498
-
499
-	/**
500
-	 * @PasswordConfirmationRequired
501
-	 *
502
-	 * @param string $appId
503
-	 * @return JSONResponse
504
-	 */
505
-	public function uninstallApp(string $appId): JSONResponse {
506
-		$appId = OC_App::cleanAppId($appId);
507
-		$result = $this->installer->removeApp($appId);
508
-		if($result !== false) {
509
-			$this->appManager->clearAppsCache();
510
-			return new JSONResponse(['data' => ['appid' => $appId]]);
511
-		}
512
-		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
513
-	}
514
-
515
-	/**
516
-	 * @param string $appId
517
-	 * @return JSONResponse
518
-	 */
519
-	public function updateApp(string $appId): JSONResponse {
520
-		$appId = OC_App::cleanAppId($appId);
521
-
522
-		$this->config->setSystemValue('maintenance', true);
523
-		try {
524
-			$result = $this->installer->updateAppstoreApp($appId);
525
-			$this->config->setSystemValue('maintenance', false);
526
-		} catch (\Exception $ex) {
527
-			$this->config->setSystemValue('maintenance', false);
528
-			return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
529
-		}
530
-
531
-		if ($result !== false) {
532
-			return new JSONResponse(['data' => ['appid' => $appId]]);
533
-		}
534
-		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
535
-	}
536
-
537
-	private function sortApps($a, $b) {
538
-		$a = (string)$a['name'];
539
-		$b = (string)$b['name'];
540
-		if ($a === $b) {
541
-			return 0;
542
-		}
543
-		return ($a < $b) ? -1 : 1;
544
-	}
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
+        \OC_Util::addVendorScript('core', 'marked/marked.min');
137
+        $params = [];
138
+        $params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
139
+        $params['updateCount'] = count($this->getAppsWithUpdates());
140
+        $params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
141
+        $params['bundles'] = $this->getBundles();
142
+        $this->navigationManager->setActiveEntry('core_apps');
143
+
144
+        $templateResponse = new TemplateResponse('settings', 'settings-vue', ['serverData' => $params]);
145
+        $policy = new ContentSecurityPolicy();
146
+        $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
147
+        $templateResponse->setContentSecurityPolicy($policy);
148
+
149
+        return $templateResponse;
150
+    }
151
+
152
+    private function getAppsWithUpdates() {
153
+        $appClass = new \OC_App();
154
+        $apps = $appClass->listAllApps();
155
+        foreach($apps as $key => $app) {
156
+            $newVersion = $this->installer->isUpdateAvailable($app['id']);
157
+            if($newVersion === false) {
158
+                unset($apps[$key]);
159
+            }
160
+        }
161
+        return $apps;
162
+    }
163
+
164
+    private function getBundles() {
165
+        $result = [];
166
+        $bundles = $this->bundleFetcher->getBundles();
167
+        foreach ($bundles as $bundle) {
168
+            $result[] = [
169
+                'name' => $bundle->getName(),
170
+                'id' => $bundle->getIdentifier(),
171
+                'appIdentifiers' => $bundle->getAppIdentifiers()
172
+            ];
173
+        }
174
+        return $result;
175
+
176
+    }
177
+
178
+    /**
179
+     * Get all available categories
180
+     *
181
+     * @return JSONResponse
182
+     */
183
+    public function listCategories(): JSONResponse {
184
+        return new JSONResponse($this->getAllCategories());
185
+    }
186
+
187
+    private function getAllCategories() {
188
+        $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
189
+
190
+        $formattedCategories = [];
191
+        $categories = $this->categoryFetcher->get();
192
+        foreach($categories as $category) {
193
+            $formattedCategories[] = [
194
+                'id' => $category['id'],
195
+                'ident' => $category['id'],
196
+                'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
197
+            ];
198
+        }
199
+
200
+        return $formattedCategories;
201
+    }
202
+
203
+    private function fetchApps() {
204
+        $appClass = new \OC_App();
205
+        $apps = $appClass->listAllApps();
206
+        foreach ($apps as $app) {
207
+            $app['installed'] = true;
208
+            $this->allApps[$app['id']] = $app;
209
+        }
210
+
211
+        $apps = $this->getAppsForCategory('');
212
+        foreach ($apps as $app) {
213
+            $app['appstore'] = true;
214
+            if (!array_key_exists($app['id'], $this->allApps)) {
215
+                $this->allApps[$app['id']] = $app;
216
+            } else {
217
+                $this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
218
+            }
219
+        }
220
+
221
+        // add bundle information
222
+        $bundles = $this->bundleFetcher->getBundles();
223
+        foreach($bundles as $bundle) {
224
+            foreach($bundle->getAppIdentifiers() as $identifier) {
225
+                foreach($this->allApps as &$app) {
226
+                    if($app['id'] === $identifier) {
227
+                        $app['bundleId'] = $bundle->getIdentifier();
228
+                        continue;
229
+                    }
230
+                }
231
+            }
232
+        }
233
+    }
234
+
235
+    private function getAllApps() {
236
+        return $this->allApps;
237
+    }
238
+    /**
239
+     * Get all available apps in a category
240
+     *
241
+     * @param string $category
242
+     * @return JSONResponse
243
+     * @throws \Exception
244
+     */
245
+    public function listApps(): JSONResponse {
246
+
247
+        $this->fetchApps();
248
+        $apps = $this->getAllApps();
249
+
250
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
251
+
252
+        // Extend existing app details
253
+        $apps = array_map(function($appData) use ($dependencyAnalyzer) {
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
+            $newVersion = $this->installer->isUpdateAvailable($appData['id']);
259
+            if($newVersion) {
260
+                $appData['update'] = $newVersion;
261
+            }
262
+
263
+            // fix groups to be an array
264
+            $groups = array();
265
+            if (is_string($appData['groups'])) {
266
+                $groups = json_decode($appData['groups']);
267
+            }
268
+            $appData['groups'] = $groups;
269
+            $appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
270
+
271
+            // fix licence vs license
272
+            if (isset($appData['license']) && !isset($appData['licence'])) {
273
+                $appData['licence'] = $appData['license'];
274
+            }
275
+
276
+            // analyse dependencies
277
+            $missing = $dependencyAnalyzer->analyze($appData);
278
+            $appData['canInstall'] = empty($missing);
279
+            $appData['missingDependencies'] = $missing;
280
+
281
+            $appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
282
+            $appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
283
+
284
+            return $appData;
285
+        }, $apps);
286
+
287
+        usort($apps, [$this, 'sortApps']);
288
+
289
+        return new JSONResponse(['apps' => $apps, 'status' => 'success']);
290
+    }
291
+
292
+    /**
293
+     * Get all apps for a category from the app store
294
+     *
295
+     * @param string $requestedCategory
296
+     * @return array
297
+     * @throws \Exception
298
+     */
299
+    private function getAppsForCategory($requestedCategory = ''): array {
300
+        $versionParser = new VersionParser();
301
+        $formattedApps = [];
302
+        $apps = $this->appFetcher->get();
303
+        foreach($apps as $app) {
304
+            // Skip all apps not in the requested category
305
+            if ($requestedCategory !== '') {
306
+                $isInCategory = false;
307
+                foreach($app['categories'] as $category) {
308
+                    if($category === $requestedCategory) {
309
+                        $isInCategory = true;
310
+                    }
311
+                }
312
+                if(!$isInCategory) {
313
+                    continue;
314
+                }
315
+            }
316
+
317
+            $nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
318
+            $nextCloudVersionDependencies = [];
319
+            if($nextCloudVersion->getMinimumVersion() !== '') {
320
+                $nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
321
+            }
322
+            if($nextCloudVersion->getMaximumVersion() !== '') {
323
+                $nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
324
+            }
325
+            $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
326
+            $existsLocally = \OC_App::getAppPath($app['id']) !== false;
327
+            $phpDependencies = [];
328
+            if($phpVersion->getMinimumVersion() !== '') {
329
+                $phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
330
+            }
331
+            if($phpVersion->getMaximumVersion() !== '') {
332
+                $phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
333
+            }
334
+            if(isset($app['releases'][0]['minIntSize'])) {
335
+                $phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
336
+            }
337
+            $authors = '';
338
+            foreach($app['authors'] as $key => $author) {
339
+                $authors .= $author['name'];
340
+                if($key !== count($app['authors']) - 1) {
341
+                    $authors .= ', ';
342
+                }
343
+            }
344
+
345
+            $currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
346
+            $enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
347
+            $groups = null;
348
+            if($enabledValue !== 'no' && $enabledValue !== 'yes') {
349
+                $groups = $enabledValue;
350
+            }
351
+
352
+            $currentVersion = '';
353
+            if($this->appManager->isInstalled($app['id'])) {
354
+                $currentVersion = $this->appManager->getAppVersion($app['id']);
355
+            } else {
356
+                $currentLanguage = $app['releases'][0]['version'];
357
+            }
358
+
359
+            $formattedApps[] = [
360
+                'id' => $app['id'],
361
+                'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
362
+                'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
363
+                'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
364
+                'license' => $app['releases'][0]['licenses'],
365
+                'author' => $authors,
366
+                'shipped' => false,
367
+                'version' => $currentVersion,
368
+                'default_enable' => '',
369
+                'types' => [],
370
+                'documentation' => [
371
+                    'admin' => $app['adminDocs'],
372
+                    'user' => $app['userDocs'],
373
+                    'developer' => $app['developerDocs']
374
+                ],
375
+                'website' => $app['website'],
376
+                'bugs' => $app['issueTracker'],
377
+                'detailpage' => $app['website'],
378
+                'dependencies' => array_merge(
379
+                    $nextCloudVersionDependencies,
380
+                    $phpDependencies
381
+                ),
382
+                'level' => ($app['isFeatured'] === true) ? 200 : 100,
383
+                'missingMaxOwnCloudVersion' => false,
384
+                'missingMinOwnCloudVersion' => false,
385
+                'canInstall' => true,
386
+                'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
387
+                'score' => $app['ratingOverall'],
388
+                'ratingNumOverall' => $app['ratingNumOverall'],
389
+                'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
390
+                'removable' => $existsLocally,
391
+                'active' => $this->appManager->isEnabledForUser($app['id']),
392
+                'needsDownload' => !$existsLocally,
393
+                'groups' => $groups,
394
+                'fromAppStore' => true,
395
+                'appstoreData' => $app,
396
+            ];
397
+        }
398
+
399
+        return $formattedApps;
400
+    }
401
+
402
+    /**
403
+     * @PasswordConfirmationRequired
404
+     *
405
+     * @param string $appId
406
+     * @param array $groups
407
+     * @return JSONResponse
408
+     */
409
+    public function enableApp(string $appId, array $groups = []): JSONResponse {
410
+        return $this->enableApps([$appId], $groups);
411
+    }
412
+
413
+    /**
414
+     * Enable one or more apps
415
+     *
416
+     * apps will be enabled for specific groups only if $groups is defined
417
+     *
418
+     * @PasswordConfirmationRequired
419
+     * @param array $appIds
420
+     * @param array $groups
421
+     * @return JSONResponse
422
+     */
423
+    public function enableApps(array $appIds, array $groups = []): JSONResponse {
424
+        try {
425
+            $updateRequired = false;
426
+
427
+            foreach ($appIds as $appId) {
428
+                $appId = OC_App::cleanAppId($appId);
429
+
430
+                // Check if app is already downloaded
431
+                /** @var Installer $installer */
432
+                $installer = \OC::$server->query(Installer::class);
433
+                $isDownloaded = $installer->isDownloaded($appId);
434
+
435
+                if(!$isDownloaded) {
436
+                    $installer->downloadApp($appId);
437
+                }
438
+
439
+                $installer->installApp($appId);
440
+
441
+                if (count($groups) > 0) {
442
+                    $this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
443
+                } else {
444
+                    $this->appManager->enableApp($appId);
445
+                }
446
+                if (\OC_App::shouldUpgrade($appId)) {
447
+                    $updateRequired = true;
448
+                }
449
+            }
450
+            return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
451
+
452
+        } catch (\Exception $e) {
453
+            $this->logger->logException($e);
454
+            return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
455
+        }
456
+    }
457
+
458
+    private function getGroupList(array $groups) {
459
+        $groupManager = \OC::$server->getGroupManager();
460
+        $groupsList = [];
461
+        foreach ($groups as $group) {
462
+            $groupItem = $groupManager->get($group);
463
+            if ($groupItem instanceof \OCP\IGroup) {
464
+                $groupsList[] = $groupManager->get($group);
465
+            }
466
+        }
467
+        return $groupsList;
468
+    }
469
+
470
+    /**
471
+     * @PasswordConfirmationRequired
472
+     *
473
+     * @param string $appId
474
+     * @return JSONResponse
475
+     */
476
+    public function disableApp(string $appId): JSONResponse {
477
+        return $this->disableApps([$appId]);
478
+    }
479
+
480
+    /**
481
+     * @PasswordConfirmationRequired
482
+     *
483
+     * @param array $appIds
484
+     * @return JSONResponse
485
+     */
486
+    public function disableApps(array $appIds): JSONResponse {
487
+        try {
488
+            foreach ($appIds as $appId) {
489
+                $appId = OC_App::cleanAppId($appId);
490
+                $this->appManager->disableApp($appId);
491
+            }
492
+            return new JSONResponse([]);
493
+        } catch (\Exception $e) {
494
+            $this->logger->logException($e);
495
+            return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
496
+        }
497
+    }
498
+
499
+    /**
500
+     * @PasswordConfirmationRequired
501
+     *
502
+     * @param string $appId
503
+     * @return JSONResponse
504
+     */
505
+    public function uninstallApp(string $appId): JSONResponse {
506
+        $appId = OC_App::cleanAppId($appId);
507
+        $result = $this->installer->removeApp($appId);
508
+        if($result !== false) {
509
+            $this->appManager->clearAppsCache();
510
+            return new JSONResponse(['data' => ['appid' => $appId]]);
511
+        }
512
+        return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
513
+    }
514
+
515
+    /**
516
+     * @param string $appId
517
+     * @return JSONResponse
518
+     */
519
+    public function updateApp(string $appId): JSONResponse {
520
+        $appId = OC_App::cleanAppId($appId);
521
+
522
+        $this->config->setSystemValue('maintenance', true);
523
+        try {
524
+            $result = $this->installer->updateAppstoreApp($appId);
525
+            $this->config->setSystemValue('maintenance', false);
526
+        } catch (\Exception $ex) {
527
+            $this->config->setSystemValue('maintenance', false);
528
+            return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
529
+        }
530
+
531
+        if ($result !== false) {
532
+            return new JSONResponse(['data' => ['appid' => $appId]]);
533
+        }
534
+        return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
535
+    }
536
+
537
+    private function sortApps($a, $b) {
538
+        $a = (string)$a['name'];
539
+        $b = (string)$b['name'];
540
+        if ($a === $b) {
541
+            return 0;
542
+        }
543
+        return ($a < $b) ? -1 : 1;
544
+    }
545 545
 
546 546
 }
Please login to merge, or discard this patch.
Spacing   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -152,9 +152,9 @@  discard block
 block discarded – undo
152 152
 	private function getAppsWithUpdates() {
153 153
 		$appClass = new \OC_App();
154 154
 		$apps = $appClass->listAllApps();
155
-		foreach($apps as $key => $app) {
155
+		foreach ($apps as $key => $app) {
156 156
 			$newVersion = $this->installer->isUpdateAvailable($app['id']);
157
-			if($newVersion === false) {
157
+			if ($newVersion === false) {
158 158
 				unset($apps[$key]);
159 159
 			}
160 160
 		}
@@ -189,7 +189,7 @@  discard block
 block discarded – undo
189 189
 
190 190
 		$formattedCategories = [];
191 191
 		$categories = $this->categoryFetcher->get();
192
-		foreach($categories as $category) {
192
+		foreach ($categories as $category) {
193 193
 			$formattedCategories[] = [
194 194
 				'id' => $category['id'],
195 195
 				'ident' => $category['id'],
@@ -220,10 +220,10 @@  discard block
 block discarded – undo
220 220
 
221 221
 		// add bundle information
222 222
 		$bundles = $this->bundleFetcher->getBundles();
223
-		foreach($bundles as $bundle) {
224
-			foreach($bundle->getAppIdentifiers() as $identifier) {
225
-				foreach($this->allApps as &$app) {
226
-					if($app['id'] === $identifier) {
223
+		foreach ($bundles as $bundle) {
224
+			foreach ($bundle->getAppIdentifiers() as $identifier) {
225
+				foreach ($this->allApps as &$app) {
226
+					if ($app['id'] === $identifier) {
227 227
 						$app['bundleId'] = $bundle->getIdentifier();
228 228
 						continue;
229 229
 					}
@@ -256,7 +256,7 @@  discard block
 block discarded – undo
256 256
 			$appData['category'] = $appstoreData['categories'];
257 257
 
258 258
 			$newVersion = $this->installer->isUpdateAvailable($appData['id']);
259
-			if($newVersion) {
259
+			if ($newVersion) {
260 260
 				$appData['update'] = $newVersion;
261 261
 			}
262 262
 
@@ -300,44 +300,44 @@  discard block
 block discarded – undo
300 300
 		$versionParser = new VersionParser();
301 301
 		$formattedApps = [];
302 302
 		$apps = $this->appFetcher->get();
303
-		foreach($apps as $app) {
303
+		foreach ($apps as $app) {
304 304
 			// Skip all apps not in the requested category
305 305
 			if ($requestedCategory !== '') {
306 306
 				$isInCategory = false;
307
-				foreach($app['categories'] as $category) {
308
-					if($category === $requestedCategory) {
307
+				foreach ($app['categories'] as $category) {
308
+					if ($category === $requestedCategory) {
309 309
 						$isInCategory = true;
310 310
 					}
311 311
 				}
312
-				if(!$isInCategory) {
312
+				if (!$isInCategory) {
313 313
 					continue;
314 314
 				}
315 315
 			}
316 316
 
317 317
 			$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
318 318
 			$nextCloudVersionDependencies = [];
319
-			if($nextCloudVersion->getMinimumVersion() !== '') {
319
+			if ($nextCloudVersion->getMinimumVersion() !== '') {
320 320
 				$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
321 321
 			}
322
-			if($nextCloudVersion->getMaximumVersion() !== '') {
322
+			if ($nextCloudVersion->getMaximumVersion() !== '') {
323 323
 				$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
324 324
 			}
325 325
 			$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
326 326
 			$existsLocally = \OC_App::getAppPath($app['id']) !== false;
327 327
 			$phpDependencies = [];
328
-			if($phpVersion->getMinimumVersion() !== '') {
328
+			if ($phpVersion->getMinimumVersion() !== '') {
329 329
 				$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
330 330
 			}
331
-			if($phpVersion->getMaximumVersion() !== '') {
331
+			if ($phpVersion->getMaximumVersion() !== '') {
332 332
 				$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
333 333
 			}
334
-			if(isset($app['releases'][0]['minIntSize'])) {
334
+			if (isset($app['releases'][0]['minIntSize'])) {
335 335
 				$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
336 336
 			}
337 337
 			$authors = '';
338
-			foreach($app['authors'] as $key => $author) {
338
+			foreach ($app['authors'] as $key => $author) {
339 339
 				$authors .= $author['name'];
340
-				if($key !== count($app['authors']) - 1) {
340
+				if ($key !== count($app['authors']) - 1) {
341 341
 					$authors .= ', ';
342 342
 				}
343 343
 			}
@@ -345,12 +345,12 @@  discard block
 block discarded – undo
345 345
 			$currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
346 346
 			$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
347 347
 			$groups = null;
348
-			if($enabledValue !== 'no' && $enabledValue !== 'yes') {
348
+			if ($enabledValue !== 'no' && $enabledValue !== 'yes') {
349 349
 				$groups = $enabledValue;
350 350
 			}
351 351
 
352 352
 			$currentVersion = '';
353
-			if($this->appManager->isInstalled($app['id'])) {
353
+			if ($this->appManager->isInstalled($app['id'])) {
354 354
 				$currentVersion = $this->appManager->getAppVersion($app['id']);
355 355
 			} else {
356 356
 				$currentLanguage = $app['releases'][0]['version'];
@@ -432,7 +432,7 @@  discard block
 block discarded – undo
432 432
 				$installer = \OC::$server->query(Installer::class);
433 433
 				$isDownloaded = $installer->isDownloaded($appId);
434 434
 
435
-				if(!$isDownloaded) {
435
+				if (!$isDownloaded) {
436 436
 					$installer->downloadApp($appId);
437 437
 				}
438 438
 
@@ -505,7 +505,7 @@  discard block
 block discarded – undo
505 505
 	public function uninstallApp(string $appId): JSONResponse {
506 506
 		$appId = OC_App::cleanAppId($appId);
507 507
 		$result = $this->installer->removeApp($appId);
508
-		if($result !== false) {
508
+		if ($result !== false) {
509 509
 			$this->appManager->clearAppsCache();
510 510
 			return new JSONResponse(['data' => ['appid' => $appId]]);
511 511
 		}
@@ -535,8 +535,8 @@  discard block
 block discarded – undo
535 535
 	}
536 536
 
537 537
 	private function sortApps($a, $b) {
538
-		$a = (string)$a['name'];
539
-		$b = (string)$b['name'];
538
+		$a = (string) $a['name'];
539
+		$b = (string) $b['name'];
540 540
 		if ($a === $b) {
541 541
 			return 0;
542 542
 		}
Please login to merge, or discard this patch.