Passed
Push — master ( b72d27...03dc79 )
by Roeland
21:59 queued 11:25
created
lib/private/Installer.php 1 patch
Indentation   +551 added lines, -551 removed lines patch added patch discarded remove patch
@@ -52,555 +52,555 @@
 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
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
116
-		$ignoreMax = in_array($appId, $ignoreMaxApps);
117
-
118
-		$version = implode('.', \OCP\Util::getVersion());
119
-		if (!\OC_App::isAppCompatible($version, $info, $ignoreMax)) {
120
-			throw new \Exception(
121
-				// TODO $l
122
-				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
123
-					[$info['name']]
124
-				)
125
-			);
126
-		}
127
-
128
-		// check for required dependencies
129
-		\OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax);
130
-		\OC_App::registerAutoloading($appId, $basedir);
131
-
132
-		//install the database
133
-		if(is_file($basedir.'/appinfo/database.xml')) {
134
-			if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
135
-				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
136
-			} else {
137
-				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
138
-			}
139
-		} else {
140
-			$ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
141
-			$ms->migrate();
142
-		}
143
-
144
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
145
-
146
-		//run appinfo/install.php
147
-		self::includeAppScript($basedir . '/appinfo/install.php');
148
-
149
-		$appData = OC_App::getAppInfo($appId);
150
-		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
151
-
152
-		//set the installed version
153
-		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
154
-		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
155
-
156
-		//set remote/public handlers
157
-		foreach($info['remote'] as $name=>$path) {
158
-			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
159
-		}
160
-		foreach($info['public'] as $name=>$path) {
161
-			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
162
-		}
163
-
164
-		OC_App::setAppTypes($info['id']);
165
-
166
-		return $info['id'];
167
-	}
168
-
169
-	/**
170
-	 * Updates the specified app from the appstore
171
-	 *
172
-	 * @param string $appId
173
-	 * @return bool
174
-	 */
175
-	public function updateAppstoreApp($appId) {
176
-		if($this->isUpdateAvailable($appId)) {
177
-			try {
178
-				$this->downloadApp($appId);
179
-			} catch (\Exception $e) {
180
-				$this->logger->logException($e, [
181
-					'level' => ILogger::ERROR,
182
-					'app' => 'core',
183
-				]);
184
-				return false;
185
-			}
186
-			return OC_App::updateApp($appId);
187
-		}
188
-
189
-		return false;
190
-	}
191
-
192
-	/**
193
-	 * Downloads an app and puts it into the app directory
194
-	 *
195
-	 * @param string $appId
196
-	 *
197
-	 * @throws \Exception If the installation was not successful
198
-	 */
199
-	public function downloadApp($appId) {
200
-		$appId = strtolower($appId);
201
-
202
-		$apps = $this->appFetcher->get();
203
-		foreach($apps as $app) {
204
-			if($app['id'] === $appId) {
205
-				// Load the certificate
206
-				$certificate = new X509();
207
-				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
208
-				$loadedCertificate = $certificate->loadX509($app['certificate']);
209
-
210
-				// Verify if the certificate has been revoked
211
-				$crl = new X509();
212
-				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
213
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
214
-				if($crl->validateSignature() !== true) {
215
-					throw new \Exception('Could not validate CRL signature');
216
-				}
217
-				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
218
-				$revoked = $crl->getRevoked($csn);
219
-				if ($revoked !== false) {
220
-					throw new \Exception(
221
-						sprintf(
222
-							'Certificate "%s" has been revoked',
223
-							$csn
224
-						)
225
-					);
226
-				}
227
-
228
-				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
229
-				if($certificate->validateSignature() !== true) {
230
-					throw new \Exception(
231
-						sprintf(
232
-							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
233
-							$appId
234
-						)
235
-					);
236
-				}
237
-
238
-				// Verify if the certificate is issued for the requested app id
239
-				$certInfo = openssl_x509_parse($app['certificate']);
240
-				if(!isset($certInfo['subject']['CN'])) {
241
-					throw new \Exception(
242
-						sprintf(
243
-							'App with id %s has a cert with no CN',
244
-							$appId
245
-						)
246
-					);
247
-				}
248
-				if($certInfo['subject']['CN'] !== $appId) {
249
-					throw new \Exception(
250
-						sprintf(
251
-							'App with id %s has a cert issued to %s',
252
-							$appId,
253
-							$certInfo['subject']['CN']
254
-						)
255
-					);
256
-				}
257
-
258
-				// Download the release
259
-				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
260
-				$client = $this->clientService->newClient();
261
-				$client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
262
-
263
-				// Check if the signature actually matches the downloaded content
264
-				$certificate = openssl_get_publickey($app['certificate']);
265
-				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
266
-				openssl_free_key($certificate);
267
-
268
-				if($verified === true) {
269
-					// Seems to match, let's proceed
270
-					$extractDir = $this->tempManager->getTemporaryFolder();
271
-					$archive = new TAR($tempFile);
272
-
273
-					if($archive) {
274
-						if (!$archive->extract($extractDir)) {
275
-							throw new \Exception(
276
-								sprintf(
277
-									'Could not extract app %s',
278
-									$appId
279
-								)
280
-							);
281
-						}
282
-						$allFiles = scandir($extractDir);
283
-						$folders = array_diff($allFiles, ['.', '..']);
284
-						$folders = array_values($folders);
285
-
286
-						if(count($folders) > 1) {
287
-							throw new \Exception(
288
-								sprintf(
289
-									'Extracted app %s has more than 1 folder',
290
-									$appId
291
-								)
292
-							);
293
-						}
294
-
295
-						// Check if appinfo/info.xml has the same app ID as well
296
-						$loadEntities = libxml_disable_entity_loader(false);
297
-						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
298
-						libxml_disable_entity_loader($loadEntities);
299
-						if((string)$xml->id !== $appId) {
300
-							throw new \Exception(
301
-								sprintf(
302
-									'App for id %s has a wrong app ID in info.xml: %s',
303
-									$appId,
304
-									(string)$xml->id
305
-								)
306
-							);
307
-						}
308
-
309
-						// Check if the version is lower than before
310
-						$currentVersion = OC_App::getAppVersion($appId);
311
-						$newVersion = (string)$xml->version;
312
-						if(version_compare($currentVersion, $newVersion) === 1) {
313
-							throw new \Exception(
314
-								sprintf(
315
-									'App for id %s has version %s and tried to update to lower version %s',
316
-									$appId,
317
-									$currentVersion,
318
-									$newVersion
319
-								)
320
-							);
321
-						}
322
-
323
-						$baseDir = OC_App::getInstallPath() . '/' . $appId;
324
-						// Remove old app with the ID if existent
325
-						OC_Helper::rmdirr($baseDir);
326
-						// Move to app folder
327
-						if(@mkdir($baseDir)) {
328
-							$extractDir .= '/' . $folders[0];
329
-							OC_Helper::copyr($extractDir, $baseDir);
330
-						}
331
-						OC_Helper::copyr($extractDir, $baseDir);
332
-						OC_Helper::rmdirr($extractDir);
333
-						return;
334
-					} else {
335
-						throw new \Exception(
336
-							sprintf(
337
-								'Could not extract app with ID %s to %s',
338
-								$appId,
339
-								$extractDir
340
-							)
341
-						);
342
-					}
343
-				} else {
344
-					// Signature does not match
345
-					throw new \Exception(
346
-						sprintf(
347
-							'App with id %s has invalid signature',
348
-							$appId
349
-						)
350
-					);
351
-				}
352
-			}
353
-		}
354
-
355
-		throw new \Exception(
356
-			sprintf(
357
-				'Could not download app %s',
358
-				$appId
359
-			)
360
-		);
361
-	}
362
-
363
-	/**
364
-	 * Check if an update for the app is available
365
-	 *
366
-	 * @param string $appId
367
-	 * @return string|false false or the version number of the update
368
-	 */
369
-	public function isUpdateAvailable($appId) {
370
-		if ($this->isInstanceReadyForUpdates === null) {
371
-			$installPath = OC_App::getInstallPath();
372
-			if ($installPath === false || $installPath === null) {
373
-				$this->isInstanceReadyForUpdates = false;
374
-			} else {
375
-				$this->isInstanceReadyForUpdates = true;
376
-			}
377
-		}
378
-
379
-		if ($this->isInstanceReadyForUpdates === false) {
380
-			return false;
381
-		}
382
-
383
-		if ($this->isInstalledFromGit($appId) === true) {
384
-			return false;
385
-		}
386
-
387
-		if ($this->apps === null) {
388
-			$this->apps = $this->appFetcher->get();
389
-		}
390
-
391
-		foreach($this->apps as $app) {
392
-			if($app['id'] === $appId) {
393
-				$currentVersion = OC_App::getAppVersion($appId);
394
-				$newestVersion = $app['releases'][0]['version'];
395
-				if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
396
-					return $newestVersion;
397
-				} else {
398
-					return false;
399
-				}
400
-			}
401
-		}
402
-
403
-		return false;
404
-	}
405
-
406
-	/**
407
-	 * Check if app has been installed from git
408
-	 * @param string $name name of the application to remove
409
-	 * @return boolean
410
-	 *
411
-	 * The function will check if the path contains a .git folder
412
-	 */
413
-	private function isInstalledFromGit($appId) {
414
-		$app = \OC_App::findAppInDirectories($appId);
415
-		if($app === false) {
416
-			return false;
417
-		}
418
-		$basedir = $app['path'].'/'.$appId;
419
-		return file_exists($basedir.'/.git/');
420
-	}
421
-
422
-	/**
423
-	 * Check if app is already downloaded
424
-	 * @param string $name name of the application to remove
425
-	 * @return boolean
426
-	 *
427
-	 * The function will check if the app is already downloaded in the apps repository
428
-	 */
429
-	public function isDownloaded($name) {
430
-		foreach(\OC::$APPSROOTS as $dir) {
431
-			$dirToTest  = $dir['path'];
432
-			$dirToTest .= '/';
433
-			$dirToTest .= $name;
434
-			$dirToTest .= '/';
435
-
436
-			if (is_dir($dirToTest)) {
437
-				return true;
438
-			}
439
-		}
440
-
441
-		return false;
442
-	}
443
-
444
-	/**
445
-	 * Removes an app
446
-	 * @param string $appId ID of the application to remove
447
-	 * @return boolean
448
-	 *
449
-	 *
450
-	 * This function works as follows
451
-	 *   -# call uninstall repair steps
452
-	 *   -# removing the files
453
-	 *
454
-	 * The function will not delete preferences, tables and the configuration,
455
-	 * this has to be done by the function oc_app_uninstall().
456
-	 */
457
-	public function removeApp($appId) {
458
-		if($this->isDownloaded( $appId )) {
459
-			if (\OC::$server->getAppManager()->isShipped($appId)) {
460
-				return false;
461
-			}
462
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
463
-			OC_Helper::rmdirr($appDir);
464
-			return true;
465
-		}else{
466
-			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR);
467
-
468
-			return false;
469
-		}
470
-
471
-	}
472
-
473
-	/**
474
-	 * Installs the app within the bundle and marks the bundle as installed
475
-	 *
476
-	 * @param Bundle $bundle
477
-	 * @throws \Exception If app could not get installed
478
-	 */
479
-	public function installAppBundle(Bundle $bundle) {
480
-		$appIds = $bundle->getAppIdentifiers();
481
-		foreach($appIds as $appId) {
482
-			if(!$this->isDownloaded($appId)) {
483
-				$this->downloadApp($appId);
484
-			}
485
-			$this->installApp($appId);
486
-			$app = new OC_App();
487
-			$app->enable($appId);
488
-		}
489
-		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
490
-		$bundles[] = $bundle->getIdentifier();
491
-		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
492
-	}
493
-
494
-	/**
495
-	 * Installs shipped apps
496
-	 *
497
-	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
498
-	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
499
-	 *                         working ownCloud at the end instead of an aborted update.
500
-	 * @return array Array of error messages (appid => Exception)
501
-	 */
502
-	public static function installShippedApps($softErrors = false) {
503
-		$appManager = \OC::$server->getAppManager();
504
-		$config = \OC::$server->getConfig();
505
-		$errors = [];
506
-		foreach(\OC::$APPSROOTS as $app_dir) {
507
-			if($dir = opendir( $app_dir['path'] )) {
508
-				while( false !== ( $filename = readdir( $dir ))) {
509
-					if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
510
-						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
511
-							if($config->getAppValue($filename, "installed_version", null) === null) {
512
-								$info=OC_App::getAppInfo($filename);
513
-								$enabled = isset($info['default_enable']);
514
-								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
515
-									  && $config->getAppValue($filename, 'enabled') !== 'no') {
516
-									if ($softErrors) {
517
-										try {
518
-											Installer::installShippedApp($filename);
519
-										} catch (HintException $e) {
520
-											if ($e->getPrevious() instanceof TableExistsException) {
521
-												$errors[$filename] = $e;
522
-												continue;
523
-											}
524
-											throw $e;
525
-										}
526
-									} else {
527
-										Installer::installShippedApp($filename);
528
-									}
529
-									$config->setAppValue($filename, 'enabled', 'yes');
530
-								}
531
-							}
532
-						}
533
-					}
534
-				}
535
-				closedir( $dir );
536
-			}
537
-		}
538
-
539
-		return $errors;
540
-	}
541
-
542
-	/**
543
-	 * install an app already placed in the app folder
544
-	 * @param string $app id of the app to install
545
-	 * @return integer
546
-	 */
547
-	public static function installShippedApp($app) {
548
-		//install the database
549
-		$appPath = OC_App::getAppPath($app);
550
-		\OC_App::registerAutoloading($app, $appPath);
551
-
552
-		if(is_file("$appPath/appinfo/database.xml")) {
553
-			try {
554
-				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
555
-			} catch (TableExistsException $e) {
556
-				throw new HintException(
557
-					'Failed to enable app ' . $app,
558
-					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
559
-					0, $e
560
-				);
561
-			}
562
-		} else {
563
-			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
564
-			$ms->migrate();
565
-		}
566
-
567
-		//run appinfo/install.php
568
-		self::includeAppScript("$appPath/appinfo/install.php");
569
-
570
-		$info = OC_App::getAppInfo($app);
571
-		if (is_null($info)) {
572
-			return false;
573
-		}
574
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
575
-
576
-		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
577
-
578
-		$config = \OC::$server->getConfig();
579
-
580
-		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
581
-		if (array_key_exists('ocsid', $info)) {
582
-			$config->setAppValue($app, 'ocsid', $info['ocsid']);
583
-		}
584
-
585
-		//set remote/public handlers
586
-		foreach($info['remote'] as $name=>$path) {
587
-			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
588
-		}
589
-		foreach($info['public'] as $name=>$path) {
590
-			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
591
-		}
592
-
593
-		OC_App::setAppTypes($info['id']);
594
-
595
-		return $info['id'];
596
-	}
597
-
598
-	/**
599
-	 * @param string $script
600
-	 */
601
-	private static function includeAppScript($script) {
602
-		if ( file_exists($script) ){
603
-			include $script;
604
-		}
605
-	}
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
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
116
+        $ignoreMax = in_array($appId, $ignoreMaxApps);
117
+
118
+        $version = implode('.', \OCP\Util::getVersion());
119
+        if (!\OC_App::isAppCompatible($version, $info, $ignoreMax)) {
120
+            throw new \Exception(
121
+                // TODO $l
122
+                $l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
123
+                    [$info['name']]
124
+                )
125
+            );
126
+        }
127
+
128
+        // check for required dependencies
129
+        \OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax);
130
+        \OC_App::registerAutoloading($appId, $basedir);
131
+
132
+        //install the database
133
+        if(is_file($basedir.'/appinfo/database.xml')) {
134
+            if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
135
+                OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
136
+            } else {
137
+                OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
138
+            }
139
+        } else {
140
+            $ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
141
+            $ms->migrate();
142
+        }
143
+
144
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
145
+
146
+        //run appinfo/install.php
147
+        self::includeAppScript($basedir . '/appinfo/install.php');
148
+
149
+        $appData = OC_App::getAppInfo($appId);
150
+        OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
151
+
152
+        //set the installed version
153
+        \OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
154
+        \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
155
+
156
+        //set remote/public handlers
157
+        foreach($info['remote'] as $name=>$path) {
158
+            \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
159
+        }
160
+        foreach($info['public'] as $name=>$path) {
161
+            \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
162
+        }
163
+
164
+        OC_App::setAppTypes($info['id']);
165
+
166
+        return $info['id'];
167
+    }
168
+
169
+    /**
170
+     * Updates the specified app from the appstore
171
+     *
172
+     * @param string $appId
173
+     * @return bool
174
+     */
175
+    public function updateAppstoreApp($appId) {
176
+        if($this->isUpdateAvailable($appId)) {
177
+            try {
178
+                $this->downloadApp($appId);
179
+            } catch (\Exception $e) {
180
+                $this->logger->logException($e, [
181
+                    'level' => ILogger::ERROR,
182
+                    'app' => 'core',
183
+                ]);
184
+                return false;
185
+            }
186
+            return OC_App::updateApp($appId);
187
+        }
188
+
189
+        return false;
190
+    }
191
+
192
+    /**
193
+     * Downloads an app and puts it into the app directory
194
+     *
195
+     * @param string $appId
196
+     *
197
+     * @throws \Exception If the installation was not successful
198
+     */
199
+    public function downloadApp($appId) {
200
+        $appId = strtolower($appId);
201
+
202
+        $apps = $this->appFetcher->get();
203
+        foreach($apps as $app) {
204
+            if($app['id'] === $appId) {
205
+                // Load the certificate
206
+                $certificate = new X509();
207
+                $certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
208
+                $loadedCertificate = $certificate->loadX509($app['certificate']);
209
+
210
+                // Verify if the certificate has been revoked
211
+                $crl = new X509();
212
+                $crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
213
+                $crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
214
+                if($crl->validateSignature() !== true) {
215
+                    throw new \Exception('Could not validate CRL signature');
216
+                }
217
+                $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
218
+                $revoked = $crl->getRevoked($csn);
219
+                if ($revoked !== false) {
220
+                    throw new \Exception(
221
+                        sprintf(
222
+                            'Certificate "%s" has been revoked',
223
+                            $csn
224
+                        )
225
+                    );
226
+                }
227
+
228
+                // Verify if the certificate has been issued by the Nextcloud Code Authority CA
229
+                if($certificate->validateSignature() !== true) {
230
+                    throw new \Exception(
231
+                        sprintf(
232
+                            'App with id %s has a certificate not issued by a trusted Code Signing Authority',
233
+                            $appId
234
+                        )
235
+                    );
236
+                }
237
+
238
+                // Verify if the certificate is issued for the requested app id
239
+                $certInfo = openssl_x509_parse($app['certificate']);
240
+                if(!isset($certInfo['subject']['CN'])) {
241
+                    throw new \Exception(
242
+                        sprintf(
243
+                            'App with id %s has a cert with no CN',
244
+                            $appId
245
+                        )
246
+                    );
247
+                }
248
+                if($certInfo['subject']['CN'] !== $appId) {
249
+                    throw new \Exception(
250
+                        sprintf(
251
+                            'App with id %s has a cert issued to %s',
252
+                            $appId,
253
+                            $certInfo['subject']['CN']
254
+                        )
255
+                    );
256
+                }
257
+
258
+                // Download the release
259
+                $tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
260
+                $client = $this->clientService->newClient();
261
+                $client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
262
+
263
+                // Check if the signature actually matches the downloaded content
264
+                $certificate = openssl_get_publickey($app['certificate']);
265
+                $verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
266
+                openssl_free_key($certificate);
267
+
268
+                if($verified === true) {
269
+                    // Seems to match, let's proceed
270
+                    $extractDir = $this->tempManager->getTemporaryFolder();
271
+                    $archive = new TAR($tempFile);
272
+
273
+                    if($archive) {
274
+                        if (!$archive->extract($extractDir)) {
275
+                            throw new \Exception(
276
+                                sprintf(
277
+                                    'Could not extract app %s',
278
+                                    $appId
279
+                                )
280
+                            );
281
+                        }
282
+                        $allFiles = scandir($extractDir);
283
+                        $folders = array_diff($allFiles, ['.', '..']);
284
+                        $folders = array_values($folders);
285
+
286
+                        if(count($folders) > 1) {
287
+                            throw new \Exception(
288
+                                sprintf(
289
+                                    'Extracted app %s has more than 1 folder',
290
+                                    $appId
291
+                                )
292
+                            );
293
+                        }
294
+
295
+                        // Check if appinfo/info.xml has the same app ID as well
296
+                        $loadEntities = libxml_disable_entity_loader(false);
297
+                        $xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
298
+                        libxml_disable_entity_loader($loadEntities);
299
+                        if((string)$xml->id !== $appId) {
300
+                            throw new \Exception(
301
+                                sprintf(
302
+                                    'App for id %s has a wrong app ID in info.xml: %s',
303
+                                    $appId,
304
+                                    (string)$xml->id
305
+                                )
306
+                            );
307
+                        }
308
+
309
+                        // Check if the version is lower than before
310
+                        $currentVersion = OC_App::getAppVersion($appId);
311
+                        $newVersion = (string)$xml->version;
312
+                        if(version_compare($currentVersion, $newVersion) === 1) {
313
+                            throw new \Exception(
314
+                                sprintf(
315
+                                    'App for id %s has version %s and tried to update to lower version %s',
316
+                                    $appId,
317
+                                    $currentVersion,
318
+                                    $newVersion
319
+                                )
320
+                            );
321
+                        }
322
+
323
+                        $baseDir = OC_App::getInstallPath() . '/' . $appId;
324
+                        // Remove old app with the ID if existent
325
+                        OC_Helper::rmdirr($baseDir);
326
+                        // Move to app folder
327
+                        if(@mkdir($baseDir)) {
328
+                            $extractDir .= '/' . $folders[0];
329
+                            OC_Helper::copyr($extractDir, $baseDir);
330
+                        }
331
+                        OC_Helper::copyr($extractDir, $baseDir);
332
+                        OC_Helper::rmdirr($extractDir);
333
+                        return;
334
+                    } else {
335
+                        throw new \Exception(
336
+                            sprintf(
337
+                                'Could not extract app with ID %s to %s',
338
+                                $appId,
339
+                                $extractDir
340
+                            )
341
+                        );
342
+                    }
343
+                } else {
344
+                    // Signature does not match
345
+                    throw new \Exception(
346
+                        sprintf(
347
+                            'App with id %s has invalid signature',
348
+                            $appId
349
+                        )
350
+                    );
351
+                }
352
+            }
353
+        }
354
+
355
+        throw new \Exception(
356
+            sprintf(
357
+                'Could not download app %s',
358
+                $appId
359
+            )
360
+        );
361
+    }
362
+
363
+    /**
364
+     * Check if an update for the app is available
365
+     *
366
+     * @param string $appId
367
+     * @return string|false false or the version number of the update
368
+     */
369
+    public function isUpdateAvailable($appId) {
370
+        if ($this->isInstanceReadyForUpdates === null) {
371
+            $installPath = OC_App::getInstallPath();
372
+            if ($installPath === false || $installPath === null) {
373
+                $this->isInstanceReadyForUpdates = false;
374
+            } else {
375
+                $this->isInstanceReadyForUpdates = true;
376
+            }
377
+        }
378
+
379
+        if ($this->isInstanceReadyForUpdates === false) {
380
+            return false;
381
+        }
382
+
383
+        if ($this->isInstalledFromGit($appId) === true) {
384
+            return false;
385
+        }
386
+
387
+        if ($this->apps === null) {
388
+            $this->apps = $this->appFetcher->get();
389
+        }
390
+
391
+        foreach($this->apps as $app) {
392
+            if($app['id'] === $appId) {
393
+                $currentVersion = OC_App::getAppVersion($appId);
394
+                $newestVersion = $app['releases'][0]['version'];
395
+                if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
396
+                    return $newestVersion;
397
+                } else {
398
+                    return false;
399
+                }
400
+            }
401
+        }
402
+
403
+        return false;
404
+    }
405
+
406
+    /**
407
+     * Check if app has been installed from git
408
+     * @param string $name name of the application to remove
409
+     * @return boolean
410
+     *
411
+     * The function will check if the path contains a .git folder
412
+     */
413
+    private function isInstalledFromGit($appId) {
414
+        $app = \OC_App::findAppInDirectories($appId);
415
+        if($app === false) {
416
+            return false;
417
+        }
418
+        $basedir = $app['path'].'/'.$appId;
419
+        return file_exists($basedir.'/.git/');
420
+    }
421
+
422
+    /**
423
+     * Check if app is already downloaded
424
+     * @param string $name name of the application to remove
425
+     * @return boolean
426
+     *
427
+     * The function will check if the app is already downloaded in the apps repository
428
+     */
429
+    public function isDownloaded($name) {
430
+        foreach(\OC::$APPSROOTS as $dir) {
431
+            $dirToTest  = $dir['path'];
432
+            $dirToTest .= '/';
433
+            $dirToTest .= $name;
434
+            $dirToTest .= '/';
435
+
436
+            if (is_dir($dirToTest)) {
437
+                return true;
438
+            }
439
+        }
440
+
441
+        return false;
442
+    }
443
+
444
+    /**
445
+     * Removes an app
446
+     * @param string $appId ID of the application to remove
447
+     * @return boolean
448
+     *
449
+     *
450
+     * This function works as follows
451
+     *   -# call uninstall repair steps
452
+     *   -# removing the files
453
+     *
454
+     * The function will not delete preferences, tables and the configuration,
455
+     * this has to be done by the function oc_app_uninstall().
456
+     */
457
+    public function removeApp($appId) {
458
+        if($this->isDownloaded( $appId )) {
459
+            if (\OC::$server->getAppManager()->isShipped($appId)) {
460
+                return false;
461
+            }
462
+            $appDir = OC_App::getInstallPath() . '/' . $appId;
463
+            OC_Helper::rmdirr($appDir);
464
+            return true;
465
+        }else{
466
+            \OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR);
467
+
468
+            return false;
469
+        }
470
+
471
+    }
472
+
473
+    /**
474
+     * Installs the app within the bundle and marks the bundle as installed
475
+     *
476
+     * @param Bundle $bundle
477
+     * @throws \Exception If app could not get installed
478
+     */
479
+    public function installAppBundle(Bundle $bundle) {
480
+        $appIds = $bundle->getAppIdentifiers();
481
+        foreach($appIds as $appId) {
482
+            if(!$this->isDownloaded($appId)) {
483
+                $this->downloadApp($appId);
484
+            }
485
+            $this->installApp($appId);
486
+            $app = new OC_App();
487
+            $app->enable($appId);
488
+        }
489
+        $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
490
+        $bundles[] = $bundle->getIdentifier();
491
+        $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
492
+    }
493
+
494
+    /**
495
+     * Installs shipped apps
496
+     *
497
+     * This function installs all apps found in the 'apps' directory that should be enabled by default;
498
+     * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
499
+     *                         working ownCloud at the end instead of an aborted update.
500
+     * @return array Array of error messages (appid => Exception)
501
+     */
502
+    public static function installShippedApps($softErrors = false) {
503
+        $appManager = \OC::$server->getAppManager();
504
+        $config = \OC::$server->getConfig();
505
+        $errors = [];
506
+        foreach(\OC::$APPSROOTS as $app_dir) {
507
+            if($dir = opendir( $app_dir['path'] )) {
508
+                while( false !== ( $filename = readdir( $dir ))) {
509
+                    if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
510
+                        if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
511
+                            if($config->getAppValue($filename, "installed_version", null) === null) {
512
+                                $info=OC_App::getAppInfo($filename);
513
+                                $enabled = isset($info['default_enable']);
514
+                                if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
515
+                                      && $config->getAppValue($filename, 'enabled') !== 'no') {
516
+                                    if ($softErrors) {
517
+                                        try {
518
+                                            Installer::installShippedApp($filename);
519
+                                        } catch (HintException $e) {
520
+                                            if ($e->getPrevious() instanceof TableExistsException) {
521
+                                                $errors[$filename] = $e;
522
+                                                continue;
523
+                                            }
524
+                                            throw $e;
525
+                                        }
526
+                                    } else {
527
+                                        Installer::installShippedApp($filename);
528
+                                    }
529
+                                    $config->setAppValue($filename, 'enabled', 'yes');
530
+                                }
531
+                            }
532
+                        }
533
+                    }
534
+                }
535
+                closedir( $dir );
536
+            }
537
+        }
538
+
539
+        return $errors;
540
+    }
541
+
542
+    /**
543
+     * install an app already placed in the app folder
544
+     * @param string $app id of the app to install
545
+     * @return integer
546
+     */
547
+    public static function installShippedApp($app) {
548
+        //install the database
549
+        $appPath = OC_App::getAppPath($app);
550
+        \OC_App::registerAutoloading($app, $appPath);
551
+
552
+        if(is_file("$appPath/appinfo/database.xml")) {
553
+            try {
554
+                OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
555
+            } catch (TableExistsException $e) {
556
+                throw new HintException(
557
+                    'Failed to enable app ' . $app,
558
+                    'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
559
+                    0, $e
560
+                );
561
+            }
562
+        } else {
563
+            $ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
564
+            $ms->migrate();
565
+        }
566
+
567
+        //run appinfo/install.php
568
+        self::includeAppScript("$appPath/appinfo/install.php");
569
+
570
+        $info = OC_App::getAppInfo($app);
571
+        if (is_null($info)) {
572
+            return false;
573
+        }
574
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
575
+
576
+        OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
577
+
578
+        $config = \OC::$server->getConfig();
579
+
580
+        $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
581
+        if (array_key_exists('ocsid', $info)) {
582
+            $config->setAppValue($app, 'ocsid', $info['ocsid']);
583
+        }
584
+
585
+        //set remote/public handlers
586
+        foreach($info['remote'] as $name=>$path) {
587
+            $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
588
+        }
589
+        foreach($info['public'] as $name=>$path) {
590
+            $config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
591
+        }
592
+
593
+        OC_App::setAppTypes($info['id']);
594
+
595
+        return $info['id'];
596
+    }
597
+
598
+    /**
599
+     * @param string $script
600
+     */
601
+    private static function includeAppScript($script) {
602
+        if ( file_exists($script) ){
603
+            include $script;
604
+        }
605
+    }
606 606
 }
Please login to merge, or discard this patch.
lib/private/App/DependencyAnalyzer.php 2 patches
Indentation   +343 added lines, -343 removed lines patch added patch discarded remove patch
@@ -34,347 +34,347 @@
 block discarded – undo
34 34
 
35 35
 class DependencyAnalyzer {
36 36
 
37
-	/** @var Platform */
38
-	private $platform;
39
-	/** @var \OCP\IL10N */
40
-	private $l;
41
-	/** @var array */
42
-	private $appInfo;
43
-
44
-	/**
45
-	 * @param Platform $platform
46
-	 * @param \OCP\IL10N $l
47
-	 */
48
-	public function __construct(Platform $platform, IL10N $l) {
49
-		$this->platform = $platform;
50
-		$this->l = $l;
51
-	}
52
-
53
-	/**
54
-	 * @param array $app
55
-	 * @returns array of missing dependencies
56
-	 */
57
-	public function analyze(array $app, bool $ignoreMax = false) {
58
-		$this->appInfo = $app;
59
-		if (isset($app['dependencies'])) {
60
-			$dependencies = $app['dependencies'];
61
-		} else {
62
-			$dependencies = [];
63
-		}
64
-
65
-		return array_merge(
66
-			$this->analyzePhpVersion($dependencies),
67
-			$this->analyzeDatabases($dependencies),
68
-			$this->analyzeCommands($dependencies),
69
-			$this->analyzeLibraries($dependencies),
70
-			$this->analyzeOS($dependencies),
71
-			$this->analyzeOC($dependencies, $app, $ignoreMax)
72
-		);
73
-	}
74
-
75
-	public function isMarkedCompatible(array $app): bool {
76
-		if (isset($app['dependencies'])) {
77
-			$dependencies = $app['dependencies'];
78
-		} else {
79
-			$dependencies = [];
80
-		}
81
-
82
-		$maxVersion = $this->getMaxVersion($dependencies, $app);
83
-		if ($maxVersion === null) {
84
-			return true;
85
-		}
86
-		return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion);
87
-	}
88
-
89
-	/**
90
-	 * Truncates both versions to the lowest common version, e.g.
91
-	 * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
92
-	 * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
93
-	 * @param string $first
94
-	 * @param string $second
95
-	 * @return string[] first element is the first version, second element is the
96
-	 * second version
97
-	 */
98
-	private function normalizeVersions($first, $second) {
99
-		$first = explode('.', $first);
100
-		$second = explode('.', $second);
101
-
102
-		// get both arrays to the same minimum size
103
-		$length = min(count($second), count($first));
104
-		$first = array_slice($first, 0, $length);
105
-		$second = array_slice($second, 0, $length);
106
-
107
-		return [implode('.', $first), implode('.', $second)];
108
-	}
109
-
110
-	/**
111
-	 * Parameters will be normalized and then passed into version_compare
112
-	 * in the same order they are specified in the method header
113
-	 * @param string $first
114
-	 * @param string $second
115
-	 * @param string $operator
116
-	 * @return bool result similar to version_compare
117
-	 */
118
-	private function compare($first, $second, $operator) {
119
-		// we can't normalize versions if one of the given parameters is not a
120
-		// version string but null. In case one parameter is null normalization
121
-		// will therefore be skipped
122
-		if ($first !== null && $second !== null) {
123
-			list($first, $second) = $this->normalizeVersions($first, $second);
124
-		}
125
-
126
-		return version_compare($first, $second, $operator);
127
-	}
128
-
129
-	/**
130
-	 * Checks if a version is bigger than another version
131
-	 * @param string $first
132
-	 * @param string $second
133
-	 * @return bool true if the first version is bigger than the second
134
-	 */
135
-	private function compareBigger($first, $second) {
136
-		return $this->compare($first, $second, '>');
137
-	}
138
-
139
-	/**
140
-	 * Checks if a version is smaller than another version
141
-	 * @param string $first
142
-	 * @param string $second
143
-	 * @return bool true if the first version is smaller than the second
144
-	 */
145
-	private function compareSmaller($first, $second) {
146
-		return $this->compare($first, $second, '<');
147
-	}
148
-
149
-	/**
150
-	 * @param array $dependencies
151
-	 * @return array
152
-	 */
153
-	private function analyzePhpVersion(array $dependencies) {
154
-		$missing = [];
155
-		if (isset($dependencies['php']['@attributes']['min-version'])) {
156
-			$minVersion = $dependencies['php']['@attributes']['min-version'];
157
-			if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
158
-				$missing[] = (string)$this->l->t('PHP %s or higher is required.', [$minVersion]);
159
-			}
160
-		}
161
-		if (isset($dependencies['php']['@attributes']['max-version'])) {
162
-			$maxVersion = $dependencies['php']['@attributes']['max-version'];
163
-			if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
164
-				$missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
165
-			}
166
-		}
167
-		if (isset($dependencies['php']['@attributes']['min-int-size'])) {
168
-			$intSize = $dependencies['php']['@attributes']['min-int-size'];
169
-			if ($intSize > $this->platform->getIntSize()*8) {
170
-				$missing[] = (string)$this->l->t('%sbit or higher PHP required.', [$intSize]);
171
-			}
172
-		}
173
-		return $missing;
174
-	}
175
-
176
-	/**
177
-	 * @param array $dependencies
178
-	 * @return array
179
-	 */
180
-	private function analyzeDatabases(array $dependencies) {
181
-		$missing = [];
182
-		if (!isset($dependencies['database'])) {
183
-			return $missing;
184
-		}
185
-
186
-		$supportedDatabases = $dependencies['database'];
187
-		if (empty($supportedDatabases)) {
188
-			return $missing;
189
-		}
190
-		if (!is_array($supportedDatabases)) {
191
-			$supportedDatabases = array($supportedDatabases);
192
-		}
193
-		$supportedDatabases = array_map(function ($db) {
194
-			return $this->getValue($db);
195
-		}, $supportedDatabases);
196
-		$currentDatabase = $this->platform->getDatabase();
197
-		if (!in_array($currentDatabase, $supportedDatabases)) {
198
-			$missing[] = (string)$this->l->t('Following databases are supported: %s', [implode(', ', $supportedDatabases)]);
199
-		}
200
-		return $missing;
201
-	}
202
-
203
-	/**
204
-	 * @param array $dependencies
205
-	 * @return array
206
-	 */
207
-	private function analyzeCommands(array $dependencies) {
208
-		$missing = [];
209
-		if (!isset($dependencies['command'])) {
210
-			return $missing;
211
-		}
212
-
213
-		$commands = $dependencies['command'];
214
-		if (!is_array($commands)) {
215
-			$commands = array($commands);
216
-		}
217
-		if (isset($commands['@value'])) {
218
-			$commands = [$commands];
219
-		}
220
-		$os = $this->platform->getOS();
221
-		foreach ($commands as $command) {
222
-			if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) {
223
-				continue;
224
-			}
225
-			$commandName = $this->getValue($command);
226
-			if (!$this->platform->isCommandKnown($commandName)) {
227
-				$missing[] = (string)$this->l->t('The command line tool %s could not be found', [$commandName]);
228
-			}
229
-		}
230
-		return $missing;
231
-	}
232
-
233
-	/**
234
-	 * @param array $dependencies
235
-	 * @return array
236
-	 */
237
-	private function analyzeLibraries(array $dependencies) {
238
-		$missing = [];
239
-		if (!isset($dependencies['lib'])) {
240
-			return $missing;
241
-		}
242
-
243
-		$libs = $dependencies['lib'];
244
-		if (!is_array($libs)) {
245
-			$libs = array($libs);
246
-		}
247
-		if (isset($libs['@value'])) {
248
-			$libs = [$libs];
249
-		}
250
-		foreach ($libs as $lib) {
251
-			$libName = $this->getValue($lib);
252
-			$libVersion = $this->platform->getLibraryVersion($libName);
253
-			if (is_null($libVersion)) {
254
-				$missing[] = $this->l->t('The library %s is not available.', [$libName]);
255
-				continue;
256
-			}
257
-
258
-			if (is_array($lib)) {
259
-				if (isset($lib['@attributes']['min-version'])) {
260
-					$minVersion = $lib['@attributes']['min-version'];
261
-					if ($this->compareSmaller($libVersion, $minVersion)) {
262
-						$missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.',
263
-							[$libName, $minVersion, $libVersion]);
264
-					}
265
-				}
266
-				if (isset($lib['@attributes']['max-version'])) {
267
-					$maxVersion = $lib['@attributes']['max-version'];
268
-					if ($this->compareBigger($libVersion, $maxVersion)) {
269
-						$missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.',
270
-							[$libName, $maxVersion, $libVersion]);
271
-					}
272
-				}
273
-			}
274
-		}
275
-		return $missing;
276
-	}
277
-
278
-	/**
279
-	 * @param array $dependencies
280
-	 * @return array
281
-	 */
282
-	private function analyzeOS(array $dependencies) {
283
-		$missing = [];
284
-		if (!isset($dependencies['os'])) {
285
-			return $missing;
286
-		}
287
-
288
-		$oss = $dependencies['os'];
289
-		if (empty($oss)) {
290
-			return $missing;
291
-		}
292
-		if (is_array($oss)) {
293
-			$oss = array_map(function ($os) {
294
-				return $this->getValue($os);
295
-			}, $oss);
296
-		} else {
297
-			$oss = array($oss);
298
-		}
299
-		$currentOS = $this->platform->getOS();
300
-		if (!in_array($currentOS, $oss)) {
301
-			$missing[] = (string)$this->l->t('Following platforms are supported: %s', [implode(', ', $oss)]);
302
-		}
303
-		return $missing;
304
-	}
305
-
306
-	/**
307
-	 * @param array $dependencies
308
-	 * @param array $appInfo
309
-	 * @return array
310
-	 */
311
-	private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax) {
312
-		$missing = [];
313
-		$minVersion = null;
314
-		if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
315
-			$minVersion = $dependencies['nextcloud']['@attributes']['min-version'];
316
-		} elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) {
317
-			$minVersion = $dependencies['owncloud']['@attributes']['min-version'];
318
-		} elseif (isset($appInfo['requiremin'])) {
319
-			$minVersion = $appInfo['requiremin'];
320
-		} elseif (isset($appInfo['require'])) {
321
-			$minVersion = $appInfo['require'];
322
-		}
323
-		$maxVersion = $this->getMaxVersion($dependencies, $appInfo);
324
-
325
-		if (!is_null($minVersion)) {
326
-			if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
327
-				$missing[] = (string)$this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]);
328
-			}
329
-		}
330
-		if (!$ignoreMax && !is_null($maxVersion)) {
331
-			if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
332
-				$missing[] = (string)$this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]);
333
-			}
334
-		}
335
-		return $missing;
336
-	}
337
-
338
-	private function getMaxVersion(array $dependencies, array $appInfo): ?string {
339
-		if (isset($dependencies['nextcloud']['@attributes']['max-version'])) {
340
-			return $dependencies['nextcloud']['@attributes']['max-version'];
341
-		}
342
-		if (isset($dependencies['owncloud']['@attributes']['max-version'])) {
343
-			return $dependencies['owncloud']['@attributes']['max-version'];
344
-		}
345
-		if (isset($appInfo['requiremax'])) {
346
-			return $appInfo['requiremax'];
347
-		}
348
-
349
-		return null;
350
-	}
351
-
352
-	/**
353
-	 * Map the internal version number to the Nextcloud version
354
-	 *
355
-	 * @param string $version
356
-	 * @return string
357
-	 */
358
-	protected function toVisibleVersion($version) {
359
-		switch ($version) {
360
-			case '9.1':
361
-				return '10';
362
-			default:
363
-				if (strpos($version, '9.1.') === 0) {
364
-					$version = '10.0.' . substr($version, 4);
365
-				}
366
-				return $version;
367
-		}
368
-	}
369
-
370
-	/**
371
-	 * @param $element
372
-	 * @return mixed
373
-	 */
374
-	private function getValue($element) {
375
-		if (isset($element['@value'])) {
376
-			return $element['@value'];
377
-		}
378
-		return (string)$element;
379
-	}
37
+    /** @var Platform */
38
+    private $platform;
39
+    /** @var \OCP\IL10N */
40
+    private $l;
41
+    /** @var array */
42
+    private $appInfo;
43
+
44
+    /**
45
+     * @param Platform $platform
46
+     * @param \OCP\IL10N $l
47
+     */
48
+    public function __construct(Platform $platform, IL10N $l) {
49
+        $this->platform = $platform;
50
+        $this->l = $l;
51
+    }
52
+
53
+    /**
54
+     * @param array $app
55
+     * @returns array of missing dependencies
56
+     */
57
+    public function analyze(array $app, bool $ignoreMax = false) {
58
+        $this->appInfo = $app;
59
+        if (isset($app['dependencies'])) {
60
+            $dependencies = $app['dependencies'];
61
+        } else {
62
+            $dependencies = [];
63
+        }
64
+
65
+        return array_merge(
66
+            $this->analyzePhpVersion($dependencies),
67
+            $this->analyzeDatabases($dependencies),
68
+            $this->analyzeCommands($dependencies),
69
+            $this->analyzeLibraries($dependencies),
70
+            $this->analyzeOS($dependencies),
71
+            $this->analyzeOC($dependencies, $app, $ignoreMax)
72
+        );
73
+    }
74
+
75
+    public function isMarkedCompatible(array $app): bool {
76
+        if (isset($app['dependencies'])) {
77
+            $dependencies = $app['dependencies'];
78
+        } else {
79
+            $dependencies = [];
80
+        }
81
+
82
+        $maxVersion = $this->getMaxVersion($dependencies, $app);
83
+        if ($maxVersion === null) {
84
+            return true;
85
+        }
86
+        return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion);
87
+    }
88
+
89
+    /**
90
+     * Truncates both versions to the lowest common version, e.g.
91
+     * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
92
+     * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
93
+     * @param string $first
94
+     * @param string $second
95
+     * @return string[] first element is the first version, second element is the
96
+     * second version
97
+     */
98
+    private function normalizeVersions($first, $second) {
99
+        $first = explode('.', $first);
100
+        $second = explode('.', $second);
101
+
102
+        // get both arrays to the same minimum size
103
+        $length = min(count($second), count($first));
104
+        $first = array_slice($first, 0, $length);
105
+        $second = array_slice($second, 0, $length);
106
+
107
+        return [implode('.', $first), implode('.', $second)];
108
+    }
109
+
110
+    /**
111
+     * Parameters will be normalized and then passed into version_compare
112
+     * in the same order they are specified in the method header
113
+     * @param string $first
114
+     * @param string $second
115
+     * @param string $operator
116
+     * @return bool result similar to version_compare
117
+     */
118
+    private function compare($first, $second, $operator) {
119
+        // we can't normalize versions if one of the given parameters is not a
120
+        // version string but null. In case one parameter is null normalization
121
+        // will therefore be skipped
122
+        if ($first !== null && $second !== null) {
123
+            list($first, $second) = $this->normalizeVersions($first, $second);
124
+        }
125
+
126
+        return version_compare($first, $second, $operator);
127
+    }
128
+
129
+    /**
130
+     * Checks if a version is bigger than another version
131
+     * @param string $first
132
+     * @param string $second
133
+     * @return bool true if the first version is bigger than the second
134
+     */
135
+    private function compareBigger($first, $second) {
136
+        return $this->compare($first, $second, '>');
137
+    }
138
+
139
+    /**
140
+     * Checks if a version is smaller than another version
141
+     * @param string $first
142
+     * @param string $second
143
+     * @return bool true if the first version is smaller than the second
144
+     */
145
+    private function compareSmaller($first, $second) {
146
+        return $this->compare($first, $second, '<');
147
+    }
148
+
149
+    /**
150
+     * @param array $dependencies
151
+     * @return array
152
+     */
153
+    private function analyzePhpVersion(array $dependencies) {
154
+        $missing = [];
155
+        if (isset($dependencies['php']['@attributes']['min-version'])) {
156
+            $minVersion = $dependencies['php']['@attributes']['min-version'];
157
+            if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
158
+                $missing[] = (string)$this->l->t('PHP %s or higher is required.', [$minVersion]);
159
+            }
160
+        }
161
+        if (isset($dependencies['php']['@attributes']['max-version'])) {
162
+            $maxVersion = $dependencies['php']['@attributes']['max-version'];
163
+            if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
164
+                $missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
165
+            }
166
+        }
167
+        if (isset($dependencies['php']['@attributes']['min-int-size'])) {
168
+            $intSize = $dependencies['php']['@attributes']['min-int-size'];
169
+            if ($intSize > $this->platform->getIntSize()*8) {
170
+                $missing[] = (string)$this->l->t('%sbit or higher PHP required.', [$intSize]);
171
+            }
172
+        }
173
+        return $missing;
174
+    }
175
+
176
+    /**
177
+     * @param array $dependencies
178
+     * @return array
179
+     */
180
+    private function analyzeDatabases(array $dependencies) {
181
+        $missing = [];
182
+        if (!isset($dependencies['database'])) {
183
+            return $missing;
184
+        }
185
+
186
+        $supportedDatabases = $dependencies['database'];
187
+        if (empty($supportedDatabases)) {
188
+            return $missing;
189
+        }
190
+        if (!is_array($supportedDatabases)) {
191
+            $supportedDatabases = array($supportedDatabases);
192
+        }
193
+        $supportedDatabases = array_map(function ($db) {
194
+            return $this->getValue($db);
195
+        }, $supportedDatabases);
196
+        $currentDatabase = $this->platform->getDatabase();
197
+        if (!in_array($currentDatabase, $supportedDatabases)) {
198
+            $missing[] = (string)$this->l->t('Following databases are supported: %s', [implode(', ', $supportedDatabases)]);
199
+        }
200
+        return $missing;
201
+    }
202
+
203
+    /**
204
+     * @param array $dependencies
205
+     * @return array
206
+     */
207
+    private function analyzeCommands(array $dependencies) {
208
+        $missing = [];
209
+        if (!isset($dependencies['command'])) {
210
+            return $missing;
211
+        }
212
+
213
+        $commands = $dependencies['command'];
214
+        if (!is_array($commands)) {
215
+            $commands = array($commands);
216
+        }
217
+        if (isset($commands['@value'])) {
218
+            $commands = [$commands];
219
+        }
220
+        $os = $this->platform->getOS();
221
+        foreach ($commands as $command) {
222
+            if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) {
223
+                continue;
224
+            }
225
+            $commandName = $this->getValue($command);
226
+            if (!$this->platform->isCommandKnown($commandName)) {
227
+                $missing[] = (string)$this->l->t('The command line tool %s could not be found', [$commandName]);
228
+            }
229
+        }
230
+        return $missing;
231
+    }
232
+
233
+    /**
234
+     * @param array $dependencies
235
+     * @return array
236
+     */
237
+    private function analyzeLibraries(array $dependencies) {
238
+        $missing = [];
239
+        if (!isset($dependencies['lib'])) {
240
+            return $missing;
241
+        }
242
+
243
+        $libs = $dependencies['lib'];
244
+        if (!is_array($libs)) {
245
+            $libs = array($libs);
246
+        }
247
+        if (isset($libs['@value'])) {
248
+            $libs = [$libs];
249
+        }
250
+        foreach ($libs as $lib) {
251
+            $libName = $this->getValue($lib);
252
+            $libVersion = $this->platform->getLibraryVersion($libName);
253
+            if (is_null($libVersion)) {
254
+                $missing[] = $this->l->t('The library %s is not available.', [$libName]);
255
+                continue;
256
+            }
257
+
258
+            if (is_array($lib)) {
259
+                if (isset($lib['@attributes']['min-version'])) {
260
+                    $minVersion = $lib['@attributes']['min-version'];
261
+                    if ($this->compareSmaller($libVersion, $minVersion)) {
262
+                        $missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.',
263
+                            [$libName, $minVersion, $libVersion]);
264
+                    }
265
+                }
266
+                if (isset($lib['@attributes']['max-version'])) {
267
+                    $maxVersion = $lib['@attributes']['max-version'];
268
+                    if ($this->compareBigger($libVersion, $maxVersion)) {
269
+                        $missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.',
270
+                            [$libName, $maxVersion, $libVersion]);
271
+                    }
272
+                }
273
+            }
274
+        }
275
+        return $missing;
276
+    }
277
+
278
+    /**
279
+     * @param array $dependencies
280
+     * @return array
281
+     */
282
+    private function analyzeOS(array $dependencies) {
283
+        $missing = [];
284
+        if (!isset($dependencies['os'])) {
285
+            return $missing;
286
+        }
287
+
288
+        $oss = $dependencies['os'];
289
+        if (empty($oss)) {
290
+            return $missing;
291
+        }
292
+        if (is_array($oss)) {
293
+            $oss = array_map(function ($os) {
294
+                return $this->getValue($os);
295
+            }, $oss);
296
+        } else {
297
+            $oss = array($oss);
298
+        }
299
+        $currentOS = $this->platform->getOS();
300
+        if (!in_array($currentOS, $oss)) {
301
+            $missing[] = (string)$this->l->t('Following platforms are supported: %s', [implode(', ', $oss)]);
302
+        }
303
+        return $missing;
304
+    }
305
+
306
+    /**
307
+     * @param array $dependencies
308
+     * @param array $appInfo
309
+     * @return array
310
+     */
311
+    private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax) {
312
+        $missing = [];
313
+        $minVersion = null;
314
+        if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
315
+            $minVersion = $dependencies['nextcloud']['@attributes']['min-version'];
316
+        } elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) {
317
+            $minVersion = $dependencies['owncloud']['@attributes']['min-version'];
318
+        } elseif (isset($appInfo['requiremin'])) {
319
+            $minVersion = $appInfo['requiremin'];
320
+        } elseif (isset($appInfo['require'])) {
321
+            $minVersion = $appInfo['require'];
322
+        }
323
+        $maxVersion = $this->getMaxVersion($dependencies, $appInfo);
324
+
325
+        if (!is_null($minVersion)) {
326
+            if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
327
+                $missing[] = (string)$this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]);
328
+            }
329
+        }
330
+        if (!$ignoreMax && !is_null($maxVersion)) {
331
+            if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
332
+                $missing[] = (string)$this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]);
333
+            }
334
+        }
335
+        return $missing;
336
+    }
337
+
338
+    private function getMaxVersion(array $dependencies, array $appInfo): ?string {
339
+        if (isset($dependencies['nextcloud']['@attributes']['max-version'])) {
340
+            return $dependencies['nextcloud']['@attributes']['max-version'];
341
+        }
342
+        if (isset($dependencies['owncloud']['@attributes']['max-version'])) {
343
+            return $dependencies['owncloud']['@attributes']['max-version'];
344
+        }
345
+        if (isset($appInfo['requiremax'])) {
346
+            return $appInfo['requiremax'];
347
+        }
348
+
349
+        return null;
350
+    }
351
+
352
+    /**
353
+     * Map the internal version number to the Nextcloud version
354
+     *
355
+     * @param string $version
356
+     * @return string
357
+     */
358
+    protected function toVisibleVersion($version) {
359
+        switch ($version) {
360
+            case '9.1':
361
+                return '10';
362
+            default:
363
+                if (strpos($version, '9.1.') === 0) {
364
+                    $version = '10.0.' . substr($version, 4);
365
+                }
366
+                return $version;
367
+        }
368
+    }
369
+
370
+    /**
371
+     * @param $element
372
+     * @return mixed
373
+     */
374
+    private function getValue($element) {
375
+        if (isset($element['@value'])) {
376
+            return $element['@value'];
377
+        }
378
+        return (string)$element;
379
+    }
380 380
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -155,19 +155,19 @@  discard block
 block discarded – undo
155 155
 		if (isset($dependencies['php']['@attributes']['min-version'])) {
156 156
 			$minVersion = $dependencies['php']['@attributes']['min-version'];
157 157
 			if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
158
-				$missing[] = (string)$this->l->t('PHP %s or higher is required.', [$minVersion]);
158
+				$missing[] = (string) $this->l->t('PHP %s or higher is required.', [$minVersion]);
159 159
 			}
160 160
 		}
161 161
 		if (isset($dependencies['php']['@attributes']['max-version'])) {
162 162
 			$maxVersion = $dependencies['php']['@attributes']['max-version'];
163 163
 			if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
164
-				$missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
164
+				$missing[] = (string) $this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
165 165
 			}
166 166
 		}
167 167
 		if (isset($dependencies['php']['@attributes']['min-int-size'])) {
168 168
 			$intSize = $dependencies['php']['@attributes']['min-int-size'];
169
-			if ($intSize > $this->platform->getIntSize()*8) {
170
-				$missing[] = (string)$this->l->t('%sbit or higher PHP required.', [$intSize]);
169
+			if ($intSize > $this->platform->getIntSize() * 8) {
170
+				$missing[] = (string) $this->l->t('%sbit or higher PHP required.', [$intSize]);
171 171
 			}
172 172
 		}
173 173
 		return $missing;
@@ -190,12 +190,12 @@  discard block
 block discarded – undo
190 190
 		if (!is_array($supportedDatabases)) {
191 191
 			$supportedDatabases = array($supportedDatabases);
192 192
 		}
193
-		$supportedDatabases = array_map(function ($db) {
193
+		$supportedDatabases = array_map(function($db) {
194 194
 			return $this->getValue($db);
195 195
 		}, $supportedDatabases);
196 196
 		$currentDatabase = $this->platform->getDatabase();
197 197
 		if (!in_array($currentDatabase, $supportedDatabases)) {
198
-			$missing[] = (string)$this->l->t('Following databases are supported: %s', [implode(', ', $supportedDatabases)]);
198
+			$missing[] = (string) $this->l->t('Following databases are supported: %s', [implode(', ', $supportedDatabases)]);
199 199
 		}
200 200
 		return $missing;
201 201
 	}
@@ -224,7 +224,7 @@  discard block
 block discarded – undo
224 224
 			}
225 225
 			$commandName = $this->getValue($command);
226 226
 			if (!$this->platform->isCommandKnown($commandName)) {
227
-				$missing[] = (string)$this->l->t('The command line tool %s could not be found', [$commandName]);
227
+				$missing[] = (string) $this->l->t('The command line tool %s could not be found', [$commandName]);
228 228
 			}
229 229
 		}
230 230
 		return $missing;
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
 			return $missing;
291 291
 		}
292 292
 		if (is_array($oss)) {
293
-			$oss = array_map(function ($os) {
293
+			$oss = array_map(function($os) {
294 294
 				return $this->getValue($os);
295 295
 			}, $oss);
296 296
 		} else {
@@ -298,7 +298,7 @@  discard block
 block discarded – undo
298 298
 		}
299 299
 		$currentOS = $this->platform->getOS();
300 300
 		if (!in_array($currentOS, $oss)) {
301
-			$missing[] = (string)$this->l->t('Following platforms are supported: %s', [implode(', ', $oss)]);
301
+			$missing[] = (string) $this->l->t('Following platforms are supported: %s', [implode(', ', $oss)]);
302 302
 		}
303 303
 		return $missing;
304 304
 	}
@@ -324,12 +324,12 @@  discard block
 block discarded – undo
324 324
 
325 325
 		if (!is_null($minVersion)) {
326 326
 			if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
327
-				$missing[] = (string)$this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]);
327
+				$missing[] = (string) $this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]);
328 328
 			}
329 329
 		}
330 330
 		if (!$ignoreMax && !is_null($maxVersion)) {
331 331
 			if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
332
-				$missing[] = (string)$this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]);
332
+				$missing[] = (string) $this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]);
333 333
 			}
334 334
 		}
335 335
 		return $missing;
@@ -361,7 +361,7 @@  discard block
 block discarded – undo
361 361
 				return '10';
362 362
 			default:
363 363
 				if (strpos($version, '9.1.') === 0) {
364
-					$version = '10.0.' . substr($version, 4);
364
+					$version = '10.0.'.substr($version, 4);
365 365
 				}
366 366
 				return $version;
367 367
 		}
@@ -375,6 +375,6 @@  discard block
 block discarded – undo
375 375
 		if (isset($element['@value'])) {
376 376
 			return $element['@value'];
377 377
 		}
378
-		return (string)$element;
378
+		return (string) $element;
379 379
 	}
380 380
 }
Please login to merge, or discard this patch.
lib/private/App/AppStore/Fetcher/AppFetcher.php 2 patches
Indentation   +104 added lines, -104 removed lines patch added patch discarded remove patch
@@ -37,119 +37,119 @@
 block discarded – undo
37 37
 
38 38
 class AppFetcher extends Fetcher {
39 39
 
40
-	/** @var CompareVersion */
41
-	private $compareVersion;
40
+    /** @var CompareVersion */
41
+    private $compareVersion;
42 42
 
43
-	/** @var bool */
44
-	private $ignoreMaxVersion;
43
+    /** @var bool */
44
+    private $ignoreMaxVersion;
45 45
 
46
-	/**
47
-	 * @param Factory $appDataFactory
48
-	 * @param IClientService $clientService
49
-	 * @param ITimeFactory $timeFactory
50
-	 * @param IConfig $config
51
-	 * @param CompareVersion $compareVersion
52
-	 * @param ILogger $logger
53
-	 */
54
-	public function __construct(Factory $appDataFactory,
55
-								IClientService $clientService,
56
-								ITimeFactory $timeFactory,
57
-								IConfig $config,
58
-								CompareVersion $compareVersion,
59
-								ILogger $logger) {
60
-		parent::__construct(
61
-			$appDataFactory,
62
-			$clientService,
63
-			$timeFactory,
64
-			$config,
65
-			$logger
66
-		);
46
+    /**
47
+     * @param Factory $appDataFactory
48
+     * @param IClientService $clientService
49
+     * @param ITimeFactory $timeFactory
50
+     * @param IConfig $config
51
+     * @param CompareVersion $compareVersion
52
+     * @param ILogger $logger
53
+     */
54
+    public function __construct(Factory $appDataFactory,
55
+                                IClientService $clientService,
56
+                                ITimeFactory $timeFactory,
57
+                                IConfig $config,
58
+                                CompareVersion $compareVersion,
59
+                                ILogger $logger) {
60
+        parent::__construct(
61
+            $appDataFactory,
62
+            $clientService,
63
+            $timeFactory,
64
+            $config,
65
+            $logger
66
+        );
67 67
 
68
-		$this->fileName = 'apps.json';
69
-		$this->setEndpoint();
70
-		$this->compareVersion = $compareVersion;
71
-		$this->ignoreMaxVersion = true;
72
-	}
68
+        $this->fileName = 'apps.json';
69
+        $this->setEndpoint();
70
+        $this->compareVersion = $compareVersion;
71
+        $this->ignoreMaxVersion = true;
72
+    }
73 73
 
74
-	/**
75
-	 * Only returns the latest compatible app release in the releases array
76
-	 *
77
-	 * @param string $ETag
78
-	 * @param string $content
79
-	 *
80
-	 * @return array
81
-	 */
82
-	protected function fetch($ETag, $content) {
83
-		/** @var mixed[] $response */
84
-		$response = parent::fetch($ETag, $content);
74
+    /**
75
+     * Only returns the latest compatible app release in the releases array
76
+     *
77
+     * @param string $ETag
78
+     * @param string $content
79
+     *
80
+     * @return array
81
+     */
82
+    protected function fetch($ETag, $content) {
83
+        /** @var mixed[] $response */
84
+        $response = parent::fetch($ETag, $content);
85 85
 
86
-		foreach($response['data'] as $dataKey => $app) {
87
-			$releases = [];
86
+        foreach($response['data'] as $dataKey => $app) {
87
+            $releases = [];
88 88
 
89
-			// Filter all compatible releases
90
-			foreach($app['releases'] as $release) {
91
-				// Exclude all nightly and pre-releases
92
-				if($release['isNightly'] === false
93
-					&& strpos($release['version'], '-') === false) {
94
-					// Exclude all versions not compatible with the current version
95
-					try {
96
-						$versionParser = new VersionParser();
97
-						$version = $versionParser->getVersion($release['rawPlatformVersionSpec']);
98
-						$ncVersion = $this->getVersion();
99
-						$min = $version->getMinimumVersion();
100
-						$max = $version->getMaximumVersion();
101
-						$minFulfilled = $this->compareVersion->isCompatible($ncVersion, $min, '>=');
102
-						$maxFulfilled = $max !== '' &&
103
-							$this->compareVersion->isCompatible($ncVersion, $max, '<=');
104
-						if ($minFulfilled && ($this->ignoreMaxVersion || $maxFulfilled)) {
105
-							$releases[] = $release;
106
-						}
107
-					} catch (\InvalidArgumentException $e) {
108
-						$this->logger->logException($e, ['app' => 'appstoreFetcher', 'level' => ILogger::WARN]);
109
-					}
110
-				}
111
-			}
89
+            // Filter all compatible releases
90
+            foreach($app['releases'] as $release) {
91
+                // Exclude all nightly and pre-releases
92
+                if($release['isNightly'] === false
93
+                    && strpos($release['version'], '-') === false) {
94
+                    // Exclude all versions not compatible with the current version
95
+                    try {
96
+                        $versionParser = new VersionParser();
97
+                        $version = $versionParser->getVersion($release['rawPlatformVersionSpec']);
98
+                        $ncVersion = $this->getVersion();
99
+                        $min = $version->getMinimumVersion();
100
+                        $max = $version->getMaximumVersion();
101
+                        $minFulfilled = $this->compareVersion->isCompatible($ncVersion, $min, '>=');
102
+                        $maxFulfilled = $max !== '' &&
103
+                            $this->compareVersion->isCompatible($ncVersion, $max, '<=');
104
+                        if ($minFulfilled && ($this->ignoreMaxVersion || $maxFulfilled)) {
105
+                            $releases[] = $release;
106
+                        }
107
+                    } catch (\InvalidArgumentException $e) {
108
+                        $this->logger->logException($e, ['app' => 'appstoreFetcher', 'level' => ILogger::WARN]);
109
+                    }
110
+                }
111
+            }
112 112
 
113
-			if (empty($releases)) {
114
-				// Remove apps that don't have a matching release
115
-				continue;
116
-			}
113
+            if (empty($releases)) {
114
+                // Remove apps that don't have a matching release
115
+                continue;
116
+            }
117 117
 
118
-			// Get the highest version
119
-			$versions = [];
120
-			foreach($releases as $release) {
121
-				$versions[] = $release['version'];
122
-			}
123
-			usort($versions, 'version_compare');
124
-			$versions = array_reverse($versions);
125
-			if(isset($versions[0])) {
126
-				$highestVersion = $versions[0];
127
-				foreach ($releases as $release) {
128
-					if ((string)$release['version'] === (string)$highestVersion) {
129
-						$response['data'][$dataKey]['releases'] = [$release];
130
-						break;
131
-					}
132
-				}
133
-			}
134
-		}
118
+            // Get the highest version
119
+            $versions = [];
120
+            foreach($releases as $release) {
121
+                $versions[] = $release['version'];
122
+            }
123
+            usort($versions, 'version_compare');
124
+            $versions = array_reverse($versions);
125
+            if(isset($versions[0])) {
126
+                $highestVersion = $versions[0];
127
+                foreach ($releases as $release) {
128
+                    if ((string)$release['version'] === (string)$highestVersion) {
129
+                        $response['data'][$dataKey]['releases'] = [$release];
130
+                        break;
131
+                    }
132
+                }
133
+            }
134
+        }
135 135
 
136
-		$response['data'] = array_values($response['data']);
137
-		return $response;
138
-	}
136
+        $response['data'] = array_values($response['data']);
137
+        return $response;
138
+    }
139 139
 
140
-	private function setEndpoint() {
141
-		$this->endpointUrl = 'https://apps.nextcloud.com/api/v1/apps.json';
142
-	}
140
+    private function setEndpoint() {
141
+        $this->endpointUrl = 'https://apps.nextcloud.com/api/v1/apps.json';
142
+    }
143 143
 
144
-	/**
145
-	 * @param string $version
146
-	 * @param string $fileName
147
-	 * @param bool $ignoreMaxVersion
148
-	 */
149
-	public function setVersion(string $version, string $fileName = 'apps.json', bool $ignoreMaxVersion = true) {
150
-		parent::setVersion($version);
151
-		$this->fileName = $fileName;
152
-		$this->ignoreMaxVersion = $ignoreMaxVersion;
153
-		$this->setEndpoint();
154
-	}
144
+    /**
145
+     * @param string $version
146
+     * @param string $fileName
147
+     * @param bool $ignoreMaxVersion
148
+     */
149
+    public function setVersion(string $version, string $fileName = 'apps.json', bool $ignoreMaxVersion = true) {
150
+        parent::setVersion($version);
151
+        $this->fileName = $fileName;
152
+        $this->ignoreMaxVersion = $ignoreMaxVersion;
153
+        $this->setEndpoint();
154
+    }
155 155
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -83,13 +83,13 @@  discard block
 block discarded – undo
83 83
 		/** @var mixed[] $response */
84 84
 		$response = parent::fetch($ETag, $content);
85 85
 
86
-		foreach($response['data'] as $dataKey => $app) {
86
+		foreach ($response['data'] as $dataKey => $app) {
87 87
 			$releases = [];
88 88
 
89 89
 			// Filter all compatible releases
90
-			foreach($app['releases'] as $release) {
90
+			foreach ($app['releases'] as $release) {
91 91
 				// Exclude all nightly and pre-releases
92
-				if($release['isNightly'] === false
92
+				if ($release['isNightly'] === false
93 93
 					&& strpos($release['version'], '-') === false) {
94 94
 					// Exclude all versions not compatible with the current version
95 95
 					try {
@@ -117,15 +117,15 @@  discard block
 block discarded – undo
117 117
 
118 118
 			// Get the highest version
119 119
 			$versions = [];
120
-			foreach($releases as $release) {
120
+			foreach ($releases as $release) {
121 121
 				$versions[] = $release['version'];
122 122
 			}
123 123
 			usort($versions, 'version_compare');
124 124
 			$versions = array_reverse($versions);
125
-			if(isset($versions[0])) {
125
+			if (isset($versions[0])) {
126 126
 				$highestVersion = $versions[0];
127 127
 				foreach ($releases as $release) {
128
-					if ((string)$release['version'] === (string)$highestVersion) {
128
+					if ((string) $release['version'] === (string) $highestVersion) {
129 129
 						$response['data'][$dataKey]['releases'] = [$release];
130 130
 						break;
131 131
 					}
Please login to merge, or discard this patch.
lib/private/legacy/app.php 1 patch
Indentation   +1038 added lines, -1038 removed lines patch added patch discarded remove patch
@@ -64,1042 +64,1042 @@
 block discarded – undo
64 64
  * upgrading and removing apps.
65 65
  */
66 66
 class OC_App {
67
-	static private $adminForms = [];
68
-	static private $personalForms = [];
69
-	static private $appTypes = [];
70
-	static private $loadedApps = [];
71
-	static private $altLogin = [];
72
-	static private $alreadyRegistered = [];
73
-	static public $autoDisabledApps = [];
74
-	const officialApp = 200;
75
-
76
-	/**
77
-	 * clean the appId
78
-	 *
79
-	 * @param string $app AppId that needs to be cleaned
80
-	 * @return string
81
-	 */
82
-	public static function cleanAppId(string $app): string {
83
-		return str_replace(array('\0', '/', '\\', '..'), '', $app);
84
-	}
85
-
86
-	/**
87
-	 * Check if an app is loaded
88
-	 *
89
-	 * @param string $app
90
-	 * @return bool
91
-	 */
92
-	public static function isAppLoaded(string $app): bool {
93
-		return in_array($app, self::$loadedApps, true);
94
-	}
95
-
96
-	/**
97
-	 * loads all apps
98
-	 *
99
-	 * @param string[] $types
100
-	 * @return bool
101
-	 *
102
-	 * This function walks through the ownCloud directory and loads all apps
103
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
104
-	 * exists.
105
-	 *
106
-	 * if $types is set to non-empty array, only apps of those types will be loaded
107
-	 */
108
-	public static function loadApps(array $types = []): bool {
109
-		if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) {
110
-			return false;
111
-		}
112
-		// Load the enabled apps here
113
-		$apps = self::getEnabledApps();
114
-
115
-		// Add each apps' folder as allowed class path
116
-		foreach($apps as $app) {
117
-			$path = self::getAppPath($app);
118
-			if($path !== false) {
119
-				self::registerAutoloading($app, $path);
120
-			}
121
-		}
122
-
123
-		// prevent app.php from printing output
124
-		ob_start();
125
-		foreach ($apps as $app) {
126
-			if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
127
-				self::loadApp($app);
128
-			}
129
-		}
130
-		ob_end_clean();
131
-
132
-		return true;
133
-	}
134
-
135
-	/**
136
-	 * load a single app
137
-	 *
138
-	 * @param string $app
139
-	 * @throws Exception
140
-	 */
141
-	public static function loadApp(string $app) {
142
-		self::$loadedApps[] = $app;
143
-		$appPath = self::getAppPath($app);
144
-		if($appPath === false) {
145
-			return;
146
-		}
147
-
148
-		// in case someone calls loadApp() directly
149
-		self::registerAutoloading($app, $appPath);
150
-
151
-		if (is_file($appPath . '/appinfo/app.php')) {
152
-			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
153
-			try {
154
-				self::requireAppFile($app);
155
-			} catch (Throwable $ex) {
156
-				\OC::$server->getLogger()->logException($ex);
157
-				if (!\OC::$server->getAppManager()->isShipped($app)) {
158
-					// Only disable apps which are not shipped
159
-					\OC::$server->getAppManager()->disableApp($app);
160
-					self::$autoDisabledApps[] = $app;
161
-				}
162
-			}
163
-			\OC::$server->getEventLogger()->end('load_app_' . $app);
164
-		}
165
-
166
-		$info = self::getAppInfo($app);
167
-		if (!empty($info['activity']['filters'])) {
168
-			foreach ($info['activity']['filters'] as $filter) {
169
-				\OC::$server->getActivityManager()->registerFilter($filter);
170
-			}
171
-		}
172
-		if (!empty($info['activity']['settings'])) {
173
-			foreach ($info['activity']['settings'] as $setting) {
174
-				\OC::$server->getActivityManager()->registerSetting($setting);
175
-			}
176
-		}
177
-		if (!empty($info['activity']['providers'])) {
178
-			foreach ($info['activity']['providers'] as $provider) {
179
-				\OC::$server->getActivityManager()->registerProvider($provider);
180
-			}
181
-		}
182
-
183
-		if (!empty($info['settings']['admin'])) {
184
-			foreach ($info['settings']['admin'] as $setting) {
185
-				\OC::$server->getSettingsManager()->registerSetting('admin', $setting);
186
-			}
187
-		}
188
-		if (!empty($info['settings']['admin-section'])) {
189
-			foreach ($info['settings']['admin-section'] as $section) {
190
-				\OC::$server->getSettingsManager()->registerSection('admin', $section);
191
-			}
192
-		}
193
-		if (!empty($info['settings']['personal'])) {
194
-			foreach ($info['settings']['personal'] as $setting) {
195
-				\OC::$server->getSettingsManager()->registerSetting('personal', $setting);
196
-			}
197
-		}
198
-		if (!empty($info['settings']['personal-section'])) {
199
-			foreach ($info['settings']['personal-section'] as $section) {
200
-				\OC::$server->getSettingsManager()->registerSection('personal', $section);
201
-			}
202
-		}
203
-
204
-		if (!empty($info['collaboration']['plugins'])) {
205
-			// deal with one or many plugin entries
206
-			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
207
-				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
208
-			foreach ($plugins as $plugin) {
209
-				if($plugin['@attributes']['type'] === 'collaborator-search') {
210
-					$pluginInfo = [
211
-						'shareType' => $plugin['@attributes']['share-type'],
212
-						'class' => $plugin['@value'],
213
-					];
214
-					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
215
-				} else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
216
-					\OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
217
-				}
218
-			}
219
-		}
220
-	}
221
-
222
-	/**
223
-	 * @internal
224
-	 * @param string $app
225
-	 * @param string $path
226
-	 */
227
-	public static function registerAutoloading(string $app, string $path) {
228
-		$key = $app . '-' . $path;
229
-		if(isset(self::$alreadyRegistered[$key])) {
230
-			return;
231
-		}
232
-
233
-		self::$alreadyRegistered[$key] = true;
234
-
235
-		// Register on PSR-4 composer autoloader
236
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
237
-		\OC::$server->registerNamespace($app, $appNamespace);
238
-
239
-		if (file_exists($path . '/composer/autoload.php')) {
240
-			require_once $path . '/composer/autoload.php';
241
-		} else {
242
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
243
-			// Register on legacy autoloader
244
-			\OC::$loader->addValidRoot($path);
245
-		}
246
-
247
-		// Register Test namespace only when testing
248
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
249
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
250
-		}
251
-	}
252
-
253
-	/**
254
-	 * Load app.php from the given app
255
-	 *
256
-	 * @param string $app app name
257
-	 * @throws Error
258
-	 */
259
-	private static function requireAppFile(string $app) {
260
-		// encapsulated here to avoid variable scope conflicts
261
-		require_once $app . '/appinfo/app.php';
262
-	}
263
-
264
-	/**
265
-	 * check if an app is of a specific type
266
-	 *
267
-	 * @param string $app
268
-	 * @param array $types
269
-	 * @return bool
270
-	 */
271
-	public static function isType(string $app, array $types): bool {
272
-		$appTypes = self::getAppTypes($app);
273
-		foreach ($types as $type) {
274
-			if (array_search($type, $appTypes) !== false) {
275
-				return true;
276
-			}
277
-		}
278
-		return false;
279
-	}
280
-
281
-	/**
282
-	 * get the types of an app
283
-	 *
284
-	 * @param string $app
285
-	 * @return array
286
-	 */
287
-	private static function getAppTypes(string $app): array {
288
-		//load the cache
289
-		if (count(self::$appTypes) == 0) {
290
-			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
291
-		}
292
-
293
-		if (isset(self::$appTypes[$app])) {
294
-			return explode(',', self::$appTypes[$app]);
295
-		}
296
-
297
-		return [];
298
-	}
299
-
300
-	/**
301
-	 * read app types from info.xml and cache them in the database
302
-	 */
303
-	public static function setAppTypes(string $app) {
304
-		$appManager = \OC::$server->getAppManager();
305
-		$appData = $appManager->getAppInfo($app);
306
-		if(!is_array($appData)) {
307
-			return;
308
-		}
309
-
310
-		if (isset($appData['types'])) {
311
-			$appTypes = implode(',', $appData['types']);
312
-		} else {
313
-			$appTypes = '';
314
-			$appData['types'] = [];
315
-		}
316
-
317
-		$config = \OC::$server->getConfig();
318
-		$config->setAppValue($app, 'types', $appTypes);
319
-
320
-		if ($appManager->hasProtectedAppType($appData['types'])) {
321
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
322
-			if ($enabled !== 'yes' && $enabled !== 'no') {
323
-				$config->setAppValue($app, 'enabled', 'yes');
324
-			}
325
-		}
326
-	}
327
-
328
-	/**
329
-	 * Returns apps enabled for the current user.
330
-	 *
331
-	 * @param bool $forceRefresh whether to refresh the cache
332
-	 * @param bool $all whether to return apps for all users, not only the
333
-	 * currently logged in one
334
-	 * @return string[]
335
-	 */
336
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
337
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
338
-			return [];
339
-		}
340
-		// in incognito mode or when logged out, $user will be false,
341
-		// which is also the case during an upgrade
342
-		$appManager = \OC::$server->getAppManager();
343
-		if ($all) {
344
-			$user = null;
345
-		} else {
346
-			$user = \OC::$server->getUserSession()->getUser();
347
-		}
348
-
349
-		if (is_null($user)) {
350
-			$apps = $appManager->getInstalledApps();
351
-		} else {
352
-			$apps = $appManager->getEnabledAppsForUser($user);
353
-		}
354
-		$apps = array_filter($apps, function ($app) {
355
-			return $app !== 'files';//we add this manually
356
-		});
357
-		sort($apps);
358
-		array_unshift($apps, 'files');
359
-		return $apps;
360
-	}
361
-
362
-	/**
363
-	 * checks whether or not an app is enabled
364
-	 *
365
-	 * @param string $app app
366
-	 * @return bool
367
-	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
368
-	 *
369
-	 * This function checks whether or not an app is enabled.
370
-	 */
371
-	public static function isEnabled(string $app): bool {
372
-		return \OC::$server->getAppManager()->isEnabledForUser($app);
373
-	}
374
-
375
-	/**
376
-	 * enables an app
377
-	 *
378
-	 * @param string $appId
379
-	 * @param array $groups (optional) when set, only these groups will have access to the app
380
-	 * @throws \Exception
381
-	 * @return void
382
-	 *
383
-	 * This function set an app as enabled in appconfig.
384
-	 */
385
-	public function enable(string $appId,
386
-						   array $groups = []) {
387
-
388
-		// Check if app is already downloaded
389
-		/** @var Installer $installer */
390
-		$installer = \OC::$server->query(Installer::class);
391
-		$isDownloaded = $installer->isDownloaded($appId);
392
-
393
-		if(!$isDownloaded) {
394
-			$installer->downloadApp($appId);
395
-		}
396
-
397
-		$installer->installApp($appId);
398
-
399
-		$appManager = \OC::$server->getAppManager();
400
-		if ($groups !== []) {
401
-			$groupManager = \OC::$server->getGroupManager();
402
-			$groupsList = [];
403
-			foreach ($groups as $group) {
404
-				$groupItem = $groupManager->get($group);
405
-				if ($groupItem instanceof \OCP\IGroup) {
406
-					$groupsList[] = $groupManager->get($group);
407
-				}
408
-			}
409
-			$appManager->enableAppForGroups($appId, $groupsList);
410
-		} else {
411
-			$appManager->enableApp($appId);
412
-		}
413
-	}
414
-
415
-	/**
416
-	 * Get the path where to install apps
417
-	 *
418
-	 * @return string|false
419
-	 */
420
-	public static function getInstallPath() {
421
-		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
422
-			return false;
423
-		}
424
-
425
-		foreach (OC::$APPSROOTS as $dir) {
426
-			if (isset($dir['writable']) && $dir['writable'] === true) {
427
-				return $dir['path'];
428
-			}
429
-		}
430
-
431
-		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
432
-		return null;
433
-	}
434
-
435
-
436
-	/**
437
-	 * search for an app in all app-directories
438
-	 *
439
-	 * @param string $appId
440
-	 * @return false|string
441
-	 */
442
-	public static function findAppInDirectories(string $appId) {
443
-		$sanitizedAppId = self::cleanAppId($appId);
444
-		if($sanitizedAppId !== $appId) {
445
-			return false;
446
-		}
447
-		static $app_dir = [];
448
-
449
-		if (isset($app_dir[$appId])) {
450
-			return $app_dir[$appId];
451
-		}
452
-
453
-		$possibleApps = [];
454
-		foreach (OC::$APPSROOTS as $dir) {
455
-			if (file_exists($dir['path'] . '/' . $appId)) {
456
-				$possibleApps[] = $dir;
457
-			}
458
-		}
459
-
460
-		if (empty($possibleApps)) {
461
-			return false;
462
-		} elseif (count($possibleApps) === 1) {
463
-			$dir = array_shift($possibleApps);
464
-			$app_dir[$appId] = $dir;
465
-			return $dir;
466
-		} else {
467
-			$versionToLoad = [];
468
-			foreach ($possibleApps as $possibleApp) {
469
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
470
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
471
-					$versionToLoad = array(
472
-						'dir' => $possibleApp,
473
-						'version' => $version,
474
-					);
475
-				}
476
-			}
477
-			$app_dir[$appId] = $versionToLoad['dir'];
478
-			return $versionToLoad['dir'];
479
-			//TODO - write test
480
-		}
481
-	}
482
-
483
-	/**
484
-	 * Get the directory for the given app.
485
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
486
-	 *
487
-	 * @param string $appId
488
-	 * @return string|false
489
-	 */
490
-	public static function getAppPath(string $appId) {
491
-		if ($appId === null || trim($appId) === '') {
492
-			return false;
493
-		}
494
-
495
-		if (($dir = self::findAppInDirectories($appId)) != false) {
496
-			return $dir['path'] . '/' . $appId;
497
-		}
498
-		return false;
499
-	}
500
-
501
-	/**
502
-	 * Get the path for the given app on the access
503
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
504
-	 *
505
-	 * @param string $appId
506
-	 * @return string|false
507
-	 */
508
-	public static function getAppWebPath(string $appId) {
509
-		if (($dir = self::findAppInDirectories($appId)) != false) {
510
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
511
-		}
512
-		return false;
513
-	}
514
-
515
-	/**
516
-	 * get the last version of the app from appinfo/info.xml
517
-	 *
518
-	 * @param string $appId
519
-	 * @param bool $useCache
520
-	 * @return string
521
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
522
-	 */
523
-	public static function getAppVersion(string $appId, bool $useCache = true): string {
524
-		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
525
-	}
526
-
527
-	/**
528
-	 * get app's version based on it's path
529
-	 *
530
-	 * @param string $path
531
-	 * @return string
532
-	 */
533
-	public static function getAppVersionByPath(string $path): string {
534
-		$infoFile = $path . '/appinfo/info.xml';
535
-		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
536
-		return isset($appData['version']) ? $appData['version'] : '';
537
-	}
538
-
539
-
540
-	/**
541
-	 * Read all app metadata from the info.xml file
542
-	 *
543
-	 * @param string $appId id of the app or the path of the info.xml file
544
-	 * @param bool $path
545
-	 * @param string $lang
546
-	 * @return array|null
547
-	 * @note all data is read from info.xml, not just pre-defined fields
548
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
549
-	 */
550
-	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
551
-		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
552
-	}
553
-
554
-	/**
555
-	 * Returns the navigation
556
-	 *
557
-	 * @return array
558
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
559
-	 *
560
-	 * This function returns an array containing all entries added. The
561
-	 * entries are sorted by the key 'order' ascending. Additional to the keys
562
-	 * given for each app the following keys exist:
563
-	 *   - active: boolean, signals if the user is on this navigation entry
564
-	 */
565
-	public static function getNavigation(): array {
566
-		return OC::$server->getNavigationManager()->getAll();
567
-	}
568
-
569
-	/**
570
-	 * Returns the Settings Navigation
571
-	 *
572
-	 * @return string[]
573
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
574
-	 *
575
-	 * This function returns an array containing all settings pages added. The
576
-	 * entries are sorted by the key 'order' ascending.
577
-	 */
578
-	public static function getSettingsNavigation(): array {
579
-		return OC::$server->getNavigationManager()->getAll('settings');
580
-	}
581
-
582
-	/**
583
-	 * get the id of loaded app
584
-	 *
585
-	 * @return string
586
-	 */
587
-	public static function getCurrentApp(): string {
588
-		$request = \OC::$server->getRequest();
589
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
590
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
591
-		if (empty($topFolder)) {
592
-			$path_info = $request->getPathInfo();
593
-			if ($path_info) {
594
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
595
-			}
596
-		}
597
-		if ($topFolder == 'apps') {
598
-			$length = strlen($topFolder);
599
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
600
-		} else {
601
-			return $topFolder;
602
-		}
603
-	}
604
-
605
-	/**
606
-	 * @param string $type
607
-	 * @return array
608
-	 */
609
-	public static function getForms(string $type): array {
610
-		$forms = [];
611
-		switch ($type) {
612
-			case 'admin':
613
-				$source = self::$adminForms;
614
-				break;
615
-			case 'personal':
616
-				$source = self::$personalForms;
617
-				break;
618
-			default:
619
-				return [];
620
-		}
621
-		foreach ($source as $form) {
622
-			$forms[] = include $form;
623
-		}
624
-		return $forms;
625
-	}
626
-
627
-	/**
628
-	 * register an admin form to be shown
629
-	 *
630
-	 * @param string $app
631
-	 * @param string $page
632
-	 */
633
-	public static function registerAdmin(string $app, string $page) {
634
-		self::$adminForms[] = $app . '/' . $page . '.php';
635
-	}
636
-
637
-	/**
638
-	 * register a personal form to be shown
639
-	 * @param string $app
640
-	 * @param string $page
641
-	 */
642
-	public static function registerPersonal(string $app, string $page) {
643
-		self::$personalForms[] = $app . '/' . $page . '.php';
644
-	}
645
-
646
-	/**
647
-	 * @param array $entry
648
-	 */
649
-	public static function registerLogIn(array $entry) {
650
-		self::$altLogin[] = $entry;
651
-	}
652
-
653
-	/**
654
-	 * @return array
655
-	 */
656
-	public static function getAlternativeLogIns(): array {
657
-		return self::$altLogin;
658
-	}
659
-
660
-	/**
661
-	 * get a list of all apps in the apps folder
662
-	 *
663
-	 * @return array an array of app names (string IDs)
664
-	 * @todo: change the name of this method to getInstalledApps, which is more accurate
665
-	 */
666
-	public static function getAllApps(): array {
667
-
668
-		$apps = [];
669
-
670
-		foreach (OC::$APPSROOTS as $apps_dir) {
671
-			if (!is_readable($apps_dir['path'])) {
672
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
673
-				continue;
674
-			}
675
-			$dh = opendir($apps_dir['path']);
676
-
677
-			if (is_resource($dh)) {
678
-				while (($file = readdir($dh)) !== false) {
679
-
680
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
681
-
682
-						$apps[] = $file;
683
-					}
684
-				}
685
-			}
686
-		}
687
-
688
-		$apps = array_unique($apps);
689
-
690
-		return $apps;
691
-	}
692
-
693
-	/**
694
-	 * List all apps, this is used in apps.php
695
-	 *
696
-	 * @return array
697
-	 */
698
-	public function listAllApps(): array {
699
-		$installedApps = OC_App::getAllApps();
700
-
701
-		$appManager = \OC::$server->getAppManager();
702
-		//we don't want to show configuration for these
703
-		$blacklist = $appManager->getAlwaysEnabledApps();
704
-		$appList = [];
705
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
706
-		$urlGenerator = \OC::$server->getURLGenerator();
707
-
708
-		foreach ($installedApps as $app) {
709
-			if (array_search($app, $blacklist) === false) {
710
-
711
-				$info = OC_App::getAppInfo($app, false, $langCode);
712
-				if (!is_array($info)) {
713
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
714
-					continue;
715
-				}
716
-
717
-				if (!isset($info['name'])) {
718
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
719
-					continue;
720
-				}
721
-
722
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
723
-				$info['groups'] = null;
724
-				if ($enabled === 'yes') {
725
-					$active = true;
726
-				} else if ($enabled === 'no') {
727
-					$active = false;
728
-				} else {
729
-					$active = true;
730
-					$info['groups'] = $enabled;
731
-				}
732
-
733
-				$info['active'] = $active;
734
-
735
-				if ($appManager->isShipped($app)) {
736
-					$info['internal'] = true;
737
-					$info['level'] = self::officialApp;
738
-					$info['removable'] = false;
739
-				} else {
740
-					$info['internal'] = false;
741
-					$info['removable'] = true;
742
-				}
743
-
744
-				$appPath = self::getAppPath($app);
745
-				if($appPath !== false) {
746
-					$appIcon = $appPath . '/img/' . $app . '.svg';
747
-					if (file_exists($appIcon)) {
748
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
749
-						$info['previewAsIcon'] = true;
750
-					} else {
751
-						$appIcon = $appPath . '/img/app.svg';
752
-						if (file_exists($appIcon)) {
753
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
754
-							$info['previewAsIcon'] = true;
755
-						}
756
-					}
757
-				}
758
-				// fix documentation
759
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
760
-					foreach ($info['documentation'] as $key => $url) {
761
-						// If it is not an absolute URL we assume it is a key
762
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
763
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
764
-							$url = $urlGenerator->linkToDocs($url);
765
-						}
766
-
767
-						$info['documentation'][$key] = $url;
768
-					}
769
-				}
770
-
771
-				$info['version'] = OC_App::getAppVersion($app);
772
-				$appList[] = $info;
773
-			}
774
-		}
775
-
776
-		return $appList;
777
-	}
778
-
779
-	public static function shouldUpgrade(string $app): bool {
780
-		$versions = self::getAppVersions();
781
-		$currentVersion = OC_App::getAppVersion($app);
782
-		if ($currentVersion && isset($versions[$app])) {
783
-			$installedVersion = $versions[$app];
784
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
785
-				return true;
786
-			}
787
-		}
788
-		return false;
789
-	}
790
-
791
-	/**
792
-	 * Adjust the number of version parts of $version1 to match
793
-	 * the number of version parts of $version2.
794
-	 *
795
-	 * @param string $version1 version to adjust
796
-	 * @param string $version2 version to take the number of parts from
797
-	 * @return string shortened $version1
798
-	 */
799
-	private static function adjustVersionParts(string $version1, string $version2): string {
800
-		$version1 = explode('.', $version1);
801
-		$version2 = explode('.', $version2);
802
-		// reduce $version1 to match the number of parts in $version2
803
-		while (count($version1) > count($version2)) {
804
-			array_pop($version1);
805
-		}
806
-		// if $version1 does not have enough parts, add some
807
-		while (count($version1) < count($version2)) {
808
-			$version1[] = '0';
809
-		}
810
-		return implode('.', $version1);
811
-	}
812
-
813
-	/**
814
-	 * Check whether the current ownCloud version matches the given
815
-	 * application's version requirements.
816
-	 *
817
-	 * The comparison is made based on the number of parts that the
818
-	 * app info version has. For example for ownCloud 6.0.3 if the
819
-	 * app info version is expecting version 6.0, the comparison is
820
-	 * made on the first two parts of the ownCloud version.
821
-	 * This means that it's possible to specify "requiremin" => 6
822
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
823
-	 *
824
-	 * @param string $ocVersion ownCloud version to check against
825
-	 * @param array $appInfo app info (from xml)
826
-	 *
827
-	 * @return boolean true if compatible, otherwise false
828
-	 */
829
-	public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
830
-		$requireMin = '';
831
-		$requireMax = '';
832
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
833
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
834
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
835
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
836
-		} else if (isset($appInfo['requiremin'])) {
837
-			$requireMin = $appInfo['requiremin'];
838
-		} else if (isset($appInfo['require'])) {
839
-			$requireMin = $appInfo['require'];
840
-		}
841
-
842
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
843
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
844
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
845
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
846
-		} else if (isset($appInfo['requiremax'])) {
847
-			$requireMax = $appInfo['requiremax'];
848
-		}
849
-
850
-		if (!empty($requireMin)
851
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
852
-		) {
853
-
854
-			return false;
855
-		}
856
-
857
-		if (!$ignoreMax && !empty($requireMax)
858
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
859
-		) {
860
-			return false;
861
-		}
862
-
863
-		return true;
864
-	}
865
-
866
-	/**
867
-	 * get the installed version of all apps
868
-	 */
869
-	public static function getAppVersions() {
870
-		static $versions;
871
-
872
-		if(!$versions) {
873
-			$appConfig = \OC::$server->getAppConfig();
874
-			$versions = $appConfig->getValues(false, 'installed_version');
875
-		}
876
-		return $versions;
877
-	}
878
-
879
-	/**
880
-	 * update the database for the app and call the update script
881
-	 *
882
-	 * @param string $appId
883
-	 * @return bool
884
-	 */
885
-	public static function updateApp(string $appId): bool {
886
-		$appPath = self::getAppPath($appId);
887
-		if($appPath === false) {
888
-			return false;
889
-		}
890
-		self::registerAutoloading($appId, $appPath);
891
-
892
-		\OC::$server->getAppManager()->clearAppsCache();
893
-		$appData = self::getAppInfo($appId);
894
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
895
-
896
-		if (file_exists($appPath . '/appinfo/database.xml')) {
897
-			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
898
-		} else {
899
-			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
900
-			$ms->migrate();
901
-		}
902
-
903
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
904
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
905
-		// update appversion in app manager
906
-		\OC::$server->getAppManager()->clearAppsCache();
907
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
908
-
909
-		// run upgrade code
910
-		if (file_exists($appPath . '/appinfo/update.php')) {
911
-			self::loadApp($appId);
912
-			include $appPath . '/appinfo/update.php';
913
-		}
914
-		self::setupBackgroundJobs($appData['background-jobs']);
915
-
916
-		//set remote/public handlers
917
-		if (array_key_exists('ocsid', $appData)) {
918
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
919
-		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
920
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
921
-		}
922
-		foreach ($appData['remote'] as $name => $path) {
923
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
924
-		}
925
-		foreach ($appData['public'] as $name => $path) {
926
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
927
-		}
928
-
929
-		self::setAppTypes($appId);
930
-
931
-		$version = \OC_App::getAppVersion($appId);
932
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
933
-
934
-		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
935
-			ManagerEvent::EVENT_APP_UPDATE, $appId
936
-		));
937
-
938
-		return true;
939
-	}
940
-
941
-	/**
942
-	 * @param string $appId
943
-	 * @param string[] $steps
944
-	 * @throws \OC\NeedsUpdateException
945
-	 */
946
-	public static function executeRepairSteps(string $appId, array $steps) {
947
-		if (empty($steps)) {
948
-			return;
949
-		}
950
-		// load the app
951
-		self::loadApp($appId);
952
-
953
-		$dispatcher = OC::$server->getEventDispatcher();
954
-
955
-		// load the steps
956
-		$r = new Repair([], $dispatcher);
957
-		foreach ($steps as $step) {
958
-			try {
959
-				$r->addStep($step);
960
-			} catch (Exception $ex) {
961
-				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
962
-				\OC::$server->getLogger()->logException($ex);
963
-			}
964
-		}
965
-		// run the steps
966
-		$r->run();
967
-	}
968
-
969
-	public static function setupBackgroundJobs(array $jobs) {
970
-		$queue = \OC::$server->getJobList();
971
-		foreach ($jobs as $job) {
972
-			$queue->add($job);
973
-		}
974
-	}
975
-
976
-	/**
977
-	 * @param string $appId
978
-	 * @param string[] $steps
979
-	 */
980
-	private static function setupLiveMigrations(string $appId, array $steps) {
981
-		$queue = \OC::$server->getJobList();
982
-		foreach ($steps as $step) {
983
-			$queue->add('OC\Migration\BackgroundRepair', [
984
-				'app' => $appId,
985
-				'step' => $step]);
986
-		}
987
-	}
988
-
989
-	/**
990
-	 * @param string $appId
991
-	 * @return \OC\Files\View|false
992
-	 */
993
-	public static function getStorage(string $appId) {
994
-		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
995
-			if (\OC::$server->getUserSession()->isLoggedIn()) {
996
-				$view = new \OC\Files\View('/' . OC_User::getUser());
997
-				if (!$view->file_exists($appId)) {
998
-					$view->mkdir($appId);
999
-				}
1000
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1001
-			} else {
1002
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1003
-				return false;
1004
-			}
1005
-		} else {
1006
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1007
-			return false;
1008
-		}
1009
-	}
1010
-
1011
-	protected static function findBestL10NOption(array $options, string $lang): string {
1012
-		// only a single option
1013
-		if (isset($options['@value'])) {
1014
-			return $options['@value'];
1015
-		}
1016
-
1017
-		$fallback = $similarLangFallback = $englishFallback = false;
1018
-
1019
-		$lang = strtolower($lang);
1020
-		$similarLang = $lang;
1021
-		if (strpos($similarLang, '_')) {
1022
-			// For "de_DE" we want to find "de" and the other way around
1023
-			$similarLang = substr($lang, 0, strpos($lang, '_'));
1024
-		}
1025
-
1026
-		foreach ($options as $option) {
1027
-			if (is_array($option)) {
1028
-				if ($fallback === false) {
1029
-					$fallback = $option['@value'];
1030
-				}
1031
-
1032
-				if (!isset($option['@attributes']['lang'])) {
1033
-					continue;
1034
-				}
1035
-
1036
-				$attributeLang = strtolower($option['@attributes']['lang']);
1037
-				if ($attributeLang === $lang) {
1038
-					return $option['@value'];
1039
-				}
1040
-
1041
-				if ($attributeLang === $similarLang) {
1042
-					$similarLangFallback = $option['@value'];
1043
-				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1044
-					if ($similarLangFallback === false) {
1045
-						$similarLangFallback =  $option['@value'];
1046
-					}
1047
-				}
1048
-			} else {
1049
-				$englishFallback = $option;
1050
-			}
1051
-		}
1052
-
1053
-		if ($similarLangFallback !== false) {
1054
-			return $similarLangFallback;
1055
-		} else if ($englishFallback !== false) {
1056
-			return $englishFallback;
1057
-		}
1058
-		return (string) $fallback;
1059
-	}
1060
-
1061
-	/**
1062
-	 * parses the app data array and enhanced the 'description' value
1063
-	 *
1064
-	 * @param array $data the app data
1065
-	 * @param string $lang
1066
-	 * @return array improved app data
1067
-	 */
1068
-	public static function parseAppInfo(array $data, $lang = null): array {
1069
-
1070
-		if ($lang && isset($data['name']) && is_array($data['name'])) {
1071
-			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1072
-		}
1073
-		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1074
-			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1075
-		}
1076
-		if ($lang && isset($data['description']) && is_array($data['description'])) {
1077
-			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1078
-		} else if (isset($data['description']) && is_string($data['description'])) {
1079
-			$data['description'] = trim($data['description']);
1080
-		} else  {
1081
-			$data['description'] = '';
1082
-		}
1083
-
1084
-		return $data;
1085
-	}
1086
-
1087
-	/**
1088
-	 * @param \OCP\IConfig $config
1089
-	 * @param \OCP\IL10N $l
1090
-	 * @param array $info
1091
-	 * @throws \Exception
1092
-	 */
1093
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1094
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1095
-		$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1096
-		if (!empty($missing)) {
1097
-			$missingMsg = implode(PHP_EOL, $missing);
1098
-			throw new \Exception(
1099
-				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1100
-					[$info['name'], $missingMsg]
1101
-				)
1102
-			);
1103
-		}
1104
-	}
67
+    static private $adminForms = [];
68
+    static private $personalForms = [];
69
+    static private $appTypes = [];
70
+    static private $loadedApps = [];
71
+    static private $altLogin = [];
72
+    static private $alreadyRegistered = [];
73
+    static public $autoDisabledApps = [];
74
+    const officialApp = 200;
75
+
76
+    /**
77
+     * clean the appId
78
+     *
79
+     * @param string $app AppId that needs to be cleaned
80
+     * @return string
81
+     */
82
+    public static function cleanAppId(string $app): string {
83
+        return str_replace(array('\0', '/', '\\', '..'), '', $app);
84
+    }
85
+
86
+    /**
87
+     * Check if an app is loaded
88
+     *
89
+     * @param string $app
90
+     * @return bool
91
+     */
92
+    public static function isAppLoaded(string $app): bool {
93
+        return in_array($app, self::$loadedApps, true);
94
+    }
95
+
96
+    /**
97
+     * loads all apps
98
+     *
99
+     * @param string[] $types
100
+     * @return bool
101
+     *
102
+     * This function walks through the ownCloud directory and loads all apps
103
+     * it can find. A directory contains an app if the file /appinfo/info.xml
104
+     * exists.
105
+     *
106
+     * if $types is set to non-empty array, only apps of those types will be loaded
107
+     */
108
+    public static function loadApps(array $types = []): bool {
109
+        if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) {
110
+            return false;
111
+        }
112
+        // Load the enabled apps here
113
+        $apps = self::getEnabledApps();
114
+
115
+        // Add each apps' folder as allowed class path
116
+        foreach($apps as $app) {
117
+            $path = self::getAppPath($app);
118
+            if($path !== false) {
119
+                self::registerAutoloading($app, $path);
120
+            }
121
+        }
122
+
123
+        // prevent app.php from printing output
124
+        ob_start();
125
+        foreach ($apps as $app) {
126
+            if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
127
+                self::loadApp($app);
128
+            }
129
+        }
130
+        ob_end_clean();
131
+
132
+        return true;
133
+    }
134
+
135
+    /**
136
+     * load a single app
137
+     *
138
+     * @param string $app
139
+     * @throws Exception
140
+     */
141
+    public static function loadApp(string $app) {
142
+        self::$loadedApps[] = $app;
143
+        $appPath = self::getAppPath($app);
144
+        if($appPath === false) {
145
+            return;
146
+        }
147
+
148
+        // in case someone calls loadApp() directly
149
+        self::registerAutoloading($app, $appPath);
150
+
151
+        if (is_file($appPath . '/appinfo/app.php')) {
152
+            \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
153
+            try {
154
+                self::requireAppFile($app);
155
+            } catch (Throwable $ex) {
156
+                \OC::$server->getLogger()->logException($ex);
157
+                if (!\OC::$server->getAppManager()->isShipped($app)) {
158
+                    // Only disable apps which are not shipped
159
+                    \OC::$server->getAppManager()->disableApp($app);
160
+                    self::$autoDisabledApps[] = $app;
161
+                }
162
+            }
163
+            \OC::$server->getEventLogger()->end('load_app_' . $app);
164
+        }
165
+
166
+        $info = self::getAppInfo($app);
167
+        if (!empty($info['activity']['filters'])) {
168
+            foreach ($info['activity']['filters'] as $filter) {
169
+                \OC::$server->getActivityManager()->registerFilter($filter);
170
+            }
171
+        }
172
+        if (!empty($info['activity']['settings'])) {
173
+            foreach ($info['activity']['settings'] as $setting) {
174
+                \OC::$server->getActivityManager()->registerSetting($setting);
175
+            }
176
+        }
177
+        if (!empty($info['activity']['providers'])) {
178
+            foreach ($info['activity']['providers'] as $provider) {
179
+                \OC::$server->getActivityManager()->registerProvider($provider);
180
+            }
181
+        }
182
+
183
+        if (!empty($info['settings']['admin'])) {
184
+            foreach ($info['settings']['admin'] as $setting) {
185
+                \OC::$server->getSettingsManager()->registerSetting('admin', $setting);
186
+            }
187
+        }
188
+        if (!empty($info['settings']['admin-section'])) {
189
+            foreach ($info['settings']['admin-section'] as $section) {
190
+                \OC::$server->getSettingsManager()->registerSection('admin', $section);
191
+            }
192
+        }
193
+        if (!empty($info['settings']['personal'])) {
194
+            foreach ($info['settings']['personal'] as $setting) {
195
+                \OC::$server->getSettingsManager()->registerSetting('personal', $setting);
196
+            }
197
+        }
198
+        if (!empty($info['settings']['personal-section'])) {
199
+            foreach ($info['settings']['personal-section'] as $section) {
200
+                \OC::$server->getSettingsManager()->registerSection('personal', $section);
201
+            }
202
+        }
203
+
204
+        if (!empty($info['collaboration']['plugins'])) {
205
+            // deal with one or many plugin entries
206
+            $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
207
+                [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
208
+            foreach ($plugins as $plugin) {
209
+                if($plugin['@attributes']['type'] === 'collaborator-search') {
210
+                    $pluginInfo = [
211
+                        'shareType' => $plugin['@attributes']['share-type'],
212
+                        'class' => $plugin['@value'],
213
+                    ];
214
+                    \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
215
+                } else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
216
+                    \OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
217
+                }
218
+            }
219
+        }
220
+    }
221
+
222
+    /**
223
+     * @internal
224
+     * @param string $app
225
+     * @param string $path
226
+     */
227
+    public static function registerAutoloading(string $app, string $path) {
228
+        $key = $app . '-' . $path;
229
+        if(isset(self::$alreadyRegistered[$key])) {
230
+            return;
231
+        }
232
+
233
+        self::$alreadyRegistered[$key] = true;
234
+
235
+        // Register on PSR-4 composer autoloader
236
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
237
+        \OC::$server->registerNamespace($app, $appNamespace);
238
+
239
+        if (file_exists($path . '/composer/autoload.php')) {
240
+            require_once $path . '/composer/autoload.php';
241
+        } else {
242
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
243
+            // Register on legacy autoloader
244
+            \OC::$loader->addValidRoot($path);
245
+        }
246
+
247
+        // Register Test namespace only when testing
248
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
249
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
250
+        }
251
+    }
252
+
253
+    /**
254
+     * Load app.php from the given app
255
+     *
256
+     * @param string $app app name
257
+     * @throws Error
258
+     */
259
+    private static function requireAppFile(string $app) {
260
+        // encapsulated here to avoid variable scope conflicts
261
+        require_once $app . '/appinfo/app.php';
262
+    }
263
+
264
+    /**
265
+     * check if an app is of a specific type
266
+     *
267
+     * @param string $app
268
+     * @param array $types
269
+     * @return bool
270
+     */
271
+    public static function isType(string $app, array $types): bool {
272
+        $appTypes = self::getAppTypes($app);
273
+        foreach ($types as $type) {
274
+            if (array_search($type, $appTypes) !== false) {
275
+                return true;
276
+            }
277
+        }
278
+        return false;
279
+    }
280
+
281
+    /**
282
+     * get the types of an app
283
+     *
284
+     * @param string $app
285
+     * @return array
286
+     */
287
+    private static function getAppTypes(string $app): array {
288
+        //load the cache
289
+        if (count(self::$appTypes) == 0) {
290
+            self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
291
+        }
292
+
293
+        if (isset(self::$appTypes[$app])) {
294
+            return explode(',', self::$appTypes[$app]);
295
+        }
296
+
297
+        return [];
298
+    }
299
+
300
+    /**
301
+     * read app types from info.xml and cache them in the database
302
+     */
303
+    public static function setAppTypes(string $app) {
304
+        $appManager = \OC::$server->getAppManager();
305
+        $appData = $appManager->getAppInfo($app);
306
+        if(!is_array($appData)) {
307
+            return;
308
+        }
309
+
310
+        if (isset($appData['types'])) {
311
+            $appTypes = implode(',', $appData['types']);
312
+        } else {
313
+            $appTypes = '';
314
+            $appData['types'] = [];
315
+        }
316
+
317
+        $config = \OC::$server->getConfig();
318
+        $config->setAppValue($app, 'types', $appTypes);
319
+
320
+        if ($appManager->hasProtectedAppType($appData['types'])) {
321
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
322
+            if ($enabled !== 'yes' && $enabled !== 'no') {
323
+                $config->setAppValue($app, 'enabled', 'yes');
324
+            }
325
+        }
326
+    }
327
+
328
+    /**
329
+     * Returns apps enabled for the current user.
330
+     *
331
+     * @param bool $forceRefresh whether to refresh the cache
332
+     * @param bool $all whether to return apps for all users, not only the
333
+     * currently logged in one
334
+     * @return string[]
335
+     */
336
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
337
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
338
+            return [];
339
+        }
340
+        // in incognito mode or when logged out, $user will be false,
341
+        // which is also the case during an upgrade
342
+        $appManager = \OC::$server->getAppManager();
343
+        if ($all) {
344
+            $user = null;
345
+        } else {
346
+            $user = \OC::$server->getUserSession()->getUser();
347
+        }
348
+
349
+        if (is_null($user)) {
350
+            $apps = $appManager->getInstalledApps();
351
+        } else {
352
+            $apps = $appManager->getEnabledAppsForUser($user);
353
+        }
354
+        $apps = array_filter($apps, function ($app) {
355
+            return $app !== 'files';//we add this manually
356
+        });
357
+        sort($apps);
358
+        array_unshift($apps, 'files');
359
+        return $apps;
360
+    }
361
+
362
+    /**
363
+     * checks whether or not an app is enabled
364
+     *
365
+     * @param string $app app
366
+     * @return bool
367
+     * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
368
+     *
369
+     * This function checks whether or not an app is enabled.
370
+     */
371
+    public static function isEnabled(string $app): bool {
372
+        return \OC::$server->getAppManager()->isEnabledForUser($app);
373
+    }
374
+
375
+    /**
376
+     * enables an app
377
+     *
378
+     * @param string $appId
379
+     * @param array $groups (optional) when set, only these groups will have access to the app
380
+     * @throws \Exception
381
+     * @return void
382
+     *
383
+     * This function set an app as enabled in appconfig.
384
+     */
385
+    public function enable(string $appId,
386
+                            array $groups = []) {
387
+
388
+        // Check if app is already downloaded
389
+        /** @var Installer $installer */
390
+        $installer = \OC::$server->query(Installer::class);
391
+        $isDownloaded = $installer->isDownloaded($appId);
392
+
393
+        if(!$isDownloaded) {
394
+            $installer->downloadApp($appId);
395
+        }
396
+
397
+        $installer->installApp($appId);
398
+
399
+        $appManager = \OC::$server->getAppManager();
400
+        if ($groups !== []) {
401
+            $groupManager = \OC::$server->getGroupManager();
402
+            $groupsList = [];
403
+            foreach ($groups as $group) {
404
+                $groupItem = $groupManager->get($group);
405
+                if ($groupItem instanceof \OCP\IGroup) {
406
+                    $groupsList[] = $groupManager->get($group);
407
+                }
408
+            }
409
+            $appManager->enableAppForGroups($appId, $groupsList);
410
+        } else {
411
+            $appManager->enableApp($appId);
412
+        }
413
+    }
414
+
415
+    /**
416
+     * Get the path where to install apps
417
+     *
418
+     * @return string|false
419
+     */
420
+    public static function getInstallPath() {
421
+        if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
422
+            return false;
423
+        }
424
+
425
+        foreach (OC::$APPSROOTS as $dir) {
426
+            if (isset($dir['writable']) && $dir['writable'] === true) {
427
+                return $dir['path'];
428
+            }
429
+        }
430
+
431
+        \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
432
+        return null;
433
+    }
434
+
435
+
436
+    /**
437
+     * search for an app in all app-directories
438
+     *
439
+     * @param string $appId
440
+     * @return false|string
441
+     */
442
+    public static function findAppInDirectories(string $appId) {
443
+        $sanitizedAppId = self::cleanAppId($appId);
444
+        if($sanitizedAppId !== $appId) {
445
+            return false;
446
+        }
447
+        static $app_dir = [];
448
+
449
+        if (isset($app_dir[$appId])) {
450
+            return $app_dir[$appId];
451
+        }
452
+
453
+        $possibleApps = [];
454
+        foreach (OC::$APPSROOTS as $dir) {
455
+            if (file_exists($dir['path'] . '/' . $appId)) {
456
+                $possibleApps[] = $dir;
457
+            }
458
+        }
459
+
460
+        if (empty($possibleApps)) {
461
+            return false;
462
+        } elseif (count($possibleApps) === 1) {
463
+            $dir = array_shift($possibleApps);
464
+            $app_dir[$appId] = $dir;
465
+            return $dir;
466
+        } else {
467
+            $versionToLoad = [];
468
+            foreach ($possibleApps as $possibleApp) {
469
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
470
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
471
+                    $versionToLoad = array(
472
+                        'dir' => $possibleApp,
473
+                        'version' => $version,
474
+                    );
475
+                }
476
+            }
477
+            $app_dir[$appId] = $versionToLoad['dir'];
478
+            return $versionToLoad['dir'];
479
+            //TODO - write test
480
+        }
481
+    }
482
+
483
+    /**
484
+     * Get the directory for the given app.
485
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
486
+     *
487
+     * @param string $appId
488
+     * @return string|false
489
+     */
490
+    public static function getAppPath(string $appId) {
491
+        if ($appId === null || trim($appId) === '') {
492
+            return false;
493
+        }
494
+
495
+        if (($dir = self::findAppInDirectories($appId)) != false) {
496
+            return $dir['path'] . '/' . $appId;
497
+        }
498
+        return false;
499
+    }
500
+
501
+    /**
502
+     * Get the path for the given app on the access
503
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
504
+     *
505
+     * @param string $appId
506
+     * @return string|false
507
+     */
508
+    public static function getAppWebPath(string $appId) {
509
+        if (($dir = self::findAppInDirectories($appId)) != false) {
510
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
511
+        }
512
+        return false;
513
+    }
514
+
515
+    /**
516
+     * get the last version of the app from appinfo/info.xml
517
+     *
518
+     * @param string $appId
519
+     * @param bool $useCache
520
+     * @return string
521
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
522
+     */
523
+    public static function getAppVersion(string $appId, bool $useCache = true): string {
524
+        return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
525
+    }
526
+
527
+    /**
528
+     * get app's version based on it's path
529
+     *
530
+     * @param string $path
531
+     * @return string
532
+     */
533
+    public static function getAppVersionByPath(string $path): string {
534
+        $infoFile = $path . '/appinfo/info.xml';
535
+        $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
536
+        return isset($appData['version']) ? $appData['version'] : '';
537
+    }
538
+
539
+
540
+    /**
541
+     * Read all app metadata from the info.xml file
542
+     *
543
+     * @param string $appId id of the app or the path of the info.xml file
544
+     * @param bool $path
545
+     * @param string $lang
546
+     * @return array|null
547
+     * @note all data is read from info.xml, not just pre-defined fields
548
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
549
+     */
550
+    public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
551
+        return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
552
+    }
553
+
554
+    /**
555
+     * Returns the navigation
556
+     *
557
+     * @return array
558
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
559
+     *
560
+     * This function returns an array containing all entries added. The
561
+     * entries are sorted by the key 'order' ascending. Additional to the keys
562
+     * given for each app the following keys exist:
563
+     *   - active: boolean, signals if the user is on this navigation entry
564
+     */
565
+    public static function getNavigation(): array {
566
+        return OC::$server->getNavigationManager()->getAll();
567
+    }
568
+
569
+    /**
570
+     * Returns the Settings Navigation
571
+     *
572
+     * @return string[]
573
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
574
+     *
575
+     * This function returns an array containing all settings pages added. The
576
+     * entries are sorted by the key 'order' ascending.
577
+     */
578
+    public static function getSettingsNavigation(): array {
579
+        return OC::$server->getNavigationManager()->getAll('settings');
580
+    }
581
+
582
+    /**
583
+     * get the id of loaded app
584
+     *
585
+     * @return string
586
+     */
587
+    public static function getCurrentApp(): string {
588
+        $request = \OC::$server->getRequest();
589
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
590
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
591
+        if (empty($topFolder)) {
592
+            $path_info = $request->getPathInfo();
593
+            if ($path_info) {
594
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
595
+            }
596
+        }
597
+        if ($topFolder == 'apps') {
598
+            $length = strlen($topFolder);
599
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
600
+        } else {
601
+            return $topFolder;
602
+        }
603
+    }
604
+
605
+    /**
606
+     * @param string $type
607
+     * @return array
608
+     */
609
+    public static function getForms(string $type): array {
610
+        $forms = [];
611
+        switch ($type) {
612
+            case 'admin':
613
+                $source = self::$adminForms;
614
+                break;
615
+            case 'personal':
616
+                $source = self::$personalForms;
617
+                break;
618
+            default:
619
+                return [];
620
+        }
621
+        foreach ($source as $form) {
622
+            $forms[] = include $form;
623
+        }
624
+        return $forms;
625
+    }
626
+
627
+    /**
628
+     * register an admin form to be shown
629
+     *
630
+     * @param string $app
631
+     * @param string $page
632
+     */
633
+    public static function registerAdmin(string $app, string $page) {
634
+        self::$adminForms[] = $app . '/' . $page . '.php';
635
+    }
636
+
637
+    /**
638
+     * register a personal form to be shown
639
+     * @param string $app
640
+     * @param string $page
641
+     */
642
+    public static function registerPersonal(string $app, string $page) {
643
+        self::$personalForms[] = $app . '/' . $page . '.php';
644
+    }
645
+
646
+    /**
647
+     * @param array $entry
648
+     */
649
+    public static function registerLogIn(array $entry) {
650
+        self::$altLogin[] = $entry;
651
+    }
652
+
653
+    /**
654
+     * @return array
655
+     */
656
+    public static function getAlternativeLogIns(): array {
657
+        return self::$altLogin;
658
+    }
659
+
660
+    /**
661
+     * get a list of all apps in the apps folder
662
+     *
663
+     * @return array an array of app names (string IDs)
664
+     * @todo: change the name of this method to getInstalledApps, which is more accurate
665
+     */
666
+    public static function getAllApps(): array {
667
+
668
+        $apps = [];
669
+
670
+        foreach (OC::$APPSROOTS as $apps_dir) {
671
+            if (!is_readable($apps_dir['path'])) {
672
+                \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
673
+                continue;
674
+            }
675
+            $dh = opendir($apps_dir['path']);
676
+
677
+            if (is_resource($dh)) {
678
+                while (($file = readdir($dh)) !== false) {
679
+
680
+                    if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
681
+
682
+                        $apps[] = $file;
683
+                    }
684
+                }
685
+            }
686
+        }
687
+
688
+        $apps = array_unique($apps);
689
+
690
+        return $apps;
691
+    }
692
+
693
+    /**
694
+     * List all apps, this is used in apps.php
695
+     *
696
+     * @return array
697
+     */
698
+    public function listAllApps(): array {
699
+        $installedApps = OC_App::getAllApps();
700
+
701
+        $appManager = \OC::$server->getAppManager();
702
+        //we don't want to show configuration for these
703
+        $blacklist = $appManager->getAlwaysEnabledApps();
704
+        $appList = [];
705
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
706
+        $urlGenerator = \OC::$server->getURLGenerator();
707
+
708
+        foreach ($installedApps as $app) {
709
+            if (array_search($app, $blacklist) === false) {
710
+
711
+                $info = OC_App::getAppInfo($app, false, $langCode);
712
+                if (!is_array($info)) {
713
+                    \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
714
+                    continue;
715
+                }
716
+
717
+                if (!isset($info['name'])) {
718
+                    \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
719
+                    continue;
720
+                }
721
+
722
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
723
+                $info['groups'] = null;
724
+                if ($enabled === 'yes') {
725
+                    $active = true;
726
+                } else if ($enabled === 'no') {
727
+                    $active = false;
728
+                } else {
729
+                    $active = true;
730
+                    $info['groups'] = $enabled;
731
+                }
732
+
733
+                $info['active'] = $active;
734
+
735
+                if ($appManager->isShipped($app)) {
736
+                    $info['internal'] = true;
737
+                    $info['level'] = self::officialApp;
738
+                    $info['removable'] = false;
739
+                } else {
740
+                    $info['internal'] = false;
741
+                    $info['removable'] = true;
742
+                }
743
+
744
+                $appPath = self::getAppPath($app);
745
+                if($appPath !== false) {
746
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
747
+                    if (file_exists($appIcon)) {
748
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
749
+                        $info['previewAsIcon'] = true;
750
+                    } else {
751
+                        $appIcon = $appPath . '/img/app.svg';
752
+                        if (file_exists($appIcon)) {
753
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
754
+                            $info['previewAsIcon'] = true;
755
+                        }
756
+                    }
757
+                }
758
+                // fix documentation
759
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
760
+                    foreach ($info['documentation'] as $key => $url) {
761
+                        // If it is not an absolute URL we assume it is a key
762
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
763
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
764
+                            $url = $urlGenerator->linkToDocs($url);
765
+                        }
766
+
767
+                        $info['documentation'][$key] = $url;
768
+                    }
769
+                }
770
+
771
+                $info['version'] = OC_App::getAppVersion($app);
772
+                $appList[] = $info;
773
+            }
774
+        }
775
+
776
+        return $appList;
777
+    }
778
+
779
+    public static function shouldUpgrade(string $app): bool {
780
+        $versions = self::getAppVersions();
781
+        $currentVersion = OC_App::getAppVersion($app);
782
+        if ($currentVersion && isset($versions[$app])) {
783
+            $installedVersion = $versions[$app];
784
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
785
+                return true;
786
+            }
787
+        }
788
+        return false;
789
+    }
790
+
791
+    /**
792
+     * Adjust the number of version parts of $version1 to match
793
+     * the number of version parts of $version2.
794
+     *
795
+     * @param string $version1 version to adjust
796
+     * @param string $version2 version to take the number of parts from
797
+     * @return string shortened $version1
798
+     */
799
+    private static function adjustVersionParts(string $version1, string $version2): string {
800
+        $version1 = explode('.', $version1);
801
+        $version2 = explode('.', $version2);
802
+        // reduce $version1 to match the number of parts in $version2
803
+        while (count($version1) > count($version2)) {
804
+            array_pop($version1);
805
+        }
806
+        // if $version1 does not have enough parts, add some
807
+        while (count($version1) < count($version2)) {
808
+            $version1[] = '0';
809
+        }
810
+        return implode('.', $version1);
811
+    }
812
+
813
+    /**
814
+     * Check whether the current ownCloud version matches the given
815
+     * application's version requirements.
816
+     *
817
+     * The comparison is made based on the number of parts that the
818
+     * app info version has. For example for ownCloud 6.0.3 if the
819
+     * app info version is expecting version 6.0, the comparison is
820
+     * made on the first two parts of the ownCloud version.
821
+     * This means that it's possible to specify "requiremin" => 6
822
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
823
+     *
824
+     * @param string $ocVersion ownCloud version to check against
825
+     * @param array $appInfo app info (from xml)
826
+     *
827
+     * @return boolean true if compatible, otherwise false
828
+     */
829
+    public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
830
+        $requireMin = '';
831
+        $requireMax = '';
832
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
833
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
834
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
835
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
836
+        } else if (isset($appInfo['requiremin'])) {
837
+            $requireMin = $appInfo['requiremin'];
838
+        } else if (isset($appInfo['require'])) {
839
+            $requireMin = $appInfo['require'];
840
+        }
841
+
842
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
843
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
844
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
845
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
846
+        } else if (isset($appInfo['requiremax'])) {
847
+            $requireMax = $appInfo['requiremax'];
848
+        }
849
+
850
+        if (!empty($requireMin)
851
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
852
+        ) {
853
+
854
+            return false;
855
+        }
856
+
857
+        if (!$ignoreMax && !empty($requireMax)
858
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
859
+        ) {
860
+            return false;
861
+        }
862
+
863
+        return true;
864
+    }
865
+
866
+    /**
867
+     * get the installed version of all apps
868
+     */
869
+    public static function getAppVersions() {
870
+        static $versions;
871
+
872
+        if(!$versions) {
873
+            $appConfig = \OC::$server->getAppConfig();
874
+            $versions = $appConfig->getValues(false, 'installed_version');
875
+        }
876
+        return $versions;
877
+    }
878
+
879
+    /**
880
+     * update the database for the app and call the update script
881
+     *
882
+     * @param string $appId
883
+     * @return bool
884
+     */
885
+    public static function updateApp(string $appId): bool {
886
+        $appPath = self::getAppPath($appId);
887
+        if($appPath === false) {
888
+            return false;
889
+        }
890
+        self::registerAutoloading($appId, $appPath);
891
+
892
+        \OC::$server->getAppManager()->clearAppsCache();
893
+        $appData = self::getAppInfo($appId);
894
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
895
+
896
+        if (file_exists($appPath . '/appinfo/database.xml')) {
897
+            OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
898
+        } else {
899
+            $ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
900
+            $ms->migrate();
901
+        }
902
+
903
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
904
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
905
+        // update appversion in app manager
906
+        \OC::$server->getAppManager()->clearAppsCache();
907
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
908
+
909
+        // run upgrade code
910
+        if (file_exists($appPath . '/appinfo/update.php')) {
911
+            self::loadApp($appId);
912
+            include $appPath . '/appinfo/update.php';
913
+        }
914
+        self::setupBackgroundJobs($appData['background-jobs']);
915
+
916
+        //set remote/public handlers
917
+        if (array_key_exists('ocsid', $appData)) {
918
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
919
+        } elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
920
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
921
+        }
922
+        foreach ($appData['remote'] as $name => $path) {
923
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
924
+        }
925
+        foreach ($appData['public'] as $name => $path) {
926
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
927
+        }
928
+
929
+        self::setAppTypes($appId);
930
+
931
+        $version = \OC_App::getAppVersion($appId);
932
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
933
+
934
+        \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
935
+            ManagerEvent::EVENT_APP_UPDATE, $appId
936
+        ));
937
+
938
+        return true;
939
+    }
940
+
941
+    /**
942
+     * @param string $appId
943
+     * @param string[] $steps
944
+     * @throws \OC\NeedsUpdateException
945
+     */
946
+    public static function executeRepairSteps(string $appId, array $steps) {
947
+        if (empty($steps)) {
948
+            return;
949
+        }
950
+        // load the app
951
+        self::loadApp($appId);
952
+
953
+        $dispatcher = OC::$server->getEventDispatcher();
954
+
955
+        // load the steps
956
+        $r = new Repair([], $dispatcher);
957
+        foreach ($steps as $step) {
958
+            try {
959
+                $r->addStep($step);
960
+            } catch (Exception $ex) {
961
+                $r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
962
+                \OC::$server->getLogger()->logException($ex);
963
+            }
964
+        }
965
+        // run the steps
966
+        $r->run();
967
+    }
968
+
969
+    public static function setupBackgroundJobs(array $jobs) {
970
+        $queue = \OC::$server->getJobList();
971
+        foreach ($jobs as $job) {
972
+            $queue->add($job);
973
+        }
974
+    }
975
+
976
+    /**
977
+     * @param string $appId
978
+     * @param string[] $steps
979
+     */
980
+    private static function setupLiveMigrations(string $appId, array $steps) {
981
+        $queue = \OC::$server->getJobList();
982
+        foreach ($steps as $step) {
983
+            $queue->add('OC\Migration\BackgroundRepair', [
984
+                'app' => $appId,
985
+                'step' => $step]);
986
+        }
987
+    }
988
+
989
+    /**
990
+     * @param string $appId
991
+     * @return \OC\Files\View|false
992
+     */
993
+    public static function getStorage(string $appId) {
994
+        if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
995
+            if (\OC::$server->getUserSession()->isLoggedIn()) {
996
+                $view = new \OC\Files\View('/' . OC_User::getUser());
997
+                if (!$view->file_exists($appId)) {
998
+                    $view->mkdir($appId);
999
+                }
1000
+                return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1001
+            } else {
1002
+                \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1003
+                return false;
1004
+            }
1005
+        } else {
1006
+            \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1007
+            return false;
1008
+        }
1009
+    }
1010
+
1011
+    protected static function findBestL10NOption(array $options, string $lang): string {
1012
+        // only a single option
1013
+        if (isset($options['@value'])) {
1014
+            return $options['@value'];
1015
+        }
1016
+
1017
+        $fallback = $similarLangFallback = $englishFallback = false;
1018
+
1019
+        $lang = strtolower($lang);
1020
+        $similarLang = $lang;
1021
+        if (strpos($similarLang, '_')) {
1022
+            // For "de_DE" we want to find "de" and the other way around
1023
+            $similarLang = substr($lang, 0, strpos($lang, '_'));
1024
+        }
1025
+
1026
+        foreach ($options as $option) {
1027
+            if (is_array($option)) {
1028
+                if ($fallback === false) {
1029
+                    $fallback = $option['@value'];
1030
+                }
1031
+
1032
+                if (!isset($option['@attributes']['lang'])) {
1033
+                    continue;
1034
+                }
1035
+
1036
+                $attributeLang = strtolower($option['@attributes']['lang']);
1037
+                if ($attributeLang === $lang) {
1038
+                    return $option['@value'];
1039
+                }
1040
+
1041
+                if ($attributeLang === $similarLang) {
1042
+                    $similarLangFallback = $option['@value'];
1043
+                } else if (strpos($attributeLang, $similarLang . '_') === 0) {
1044
+                    if ($similarLangFallback === false) {
1045
+                        $similarLangFallback =  $option['@value'];
1046
+                    }
1047
+                }
1048
+            } else {
1049
+                $englishFallback = $option;
1050
+            }
1051
+        }
1052
+
1053
+        if ($similarLangFallback !== false) {
1054
+            return $similarLangFallback;
1055
+        } else if ($englishFallback !== false) {
1056
+            return $englishFallback;
1057
+        }
1058
+        return (string) $fallback;
1059
+    }
1060
+
1061
+    /**
1062
+     * parses the app data array and enhanced the 'description' value
1063
+     *
1064
+     * @param array $data the app data
1065
+     * @param string $lang
1066
+     * @return array improved app data
1067
+     */
1068
+    public static function parseAppInfo(array $data, $lang = null): array {
1069
+
1070
+        if ($lang && isset($data['name']) && is_array($data['name'])) {
1071
+            $data['name'] = self::findBestL10NOption($data['name'], $lang);
1072
+        }
1073
+        if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1074
+            $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1075
+        }
1076
+        if ($lang && isset($data['description']) && is_array($data['description'])) {
1077
+            $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1078
+        } else if (isset($data['description']) && is_string($data['description'])) {
1079
+            $data['description'] = trim($data['description']);
1080
+        } else  {
1081
+            $data['description'] = '';
1082
+        }
1083
+
1084
+        return $data;
1085
+    }
1086
+
1087
+    /**
1088
+     * @param \OCP\IConfig $config
1089
+     * @param \OCP\IL10N $l
1090
+     * @param array $info
1091
+     * @throws \Exception
1092
+     */
1093
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1094
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1095
+        $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1096
+        if (!empty($missing)) {
1097
+            $missingMsg = implode(PHP_EOL, $missing);
1098
+            throw new \Exception(
1099
+                $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1100
+                    [$info['name'], $missingMsg]
1101
+                )
1102
+            );
1103
+        }
1104
+    }
1105 1105
 }
Please login to merge, or discard this patch.
apps/updatenotification/lib/Controller/APIController.php 1 patch
Indentation   +98 added lines, -98 removed lines patch added patch discarded remove patch
@@ -33,102 +33,102 @@
 block discarded – undo
33 33
 
34 34
 class APIController extends OCSController {
35 35
 
36
-	/** @var IConfig */
37
-	protected $config;
38
-
39
-	/** @var IAppManager */
40
-	protected $appManager;
41
-
42
-	/** @var AppFetcher */
43
-	protected $appFetcher;
44
-
45
-	/**
46
-	 * @param string $appName
47
-	 * @param IRequest $request
48
-	 * @param IConfig $config
49
-	 * @param IAppManager $appManager
50
-	 * @param AppFetcher $appFetcher
51
-	 */
52
-	public function __construct($appName,
53
-								IRequest $request,
54
-								IConfig $config,
55
-								IAppManager $appManager,
56
-								AppFetcher $appFetcher) {
57
-		parent::__construct($appName, $request);
58
-
59
-		$this->config = $config;
60
-		$this->appManager = $appManager;
61
-		$this->appFetcher = $appFetcher;
62
-	}
63
-
64
-	/**
65
-	 * @param string $newVersion
66
-	 * @return DataResponse
67
-	 */
68
-	public function getAppList(string $newVersion): DataResponse {
69
-		if (!$this->config->getSystemValue('appstoreenabled', true)) {
70
-			return new DataResponse([
71
-				'appstore_disabled' => true,
72
-			], Http::STATUS_NOT_FOUND);
73
-		}
74
-
75
-		// Get list of installed custom apps
76
-		$installedApps = $this->appManager->getInstalledApps();
77
-		$installedApps = array_filter($installedApps, function($app) {
78
-			try {
79
-				$this->appManager->getAppPath($app);
80
-			} catch (AppPathNotFoundException $e) {
81
-				return false;
82
-			}
83
-			return !$this->appManager->isShipped($app);
84
-		});
85
-
86
-		if (empty($installedApps)) {
87
-			return new DataResponse([
88
-				'missing' => [],
89
-				'available' => [],
90
-			]);
91
-		}
92
-
93
-		$this->appFetcher->setVersion($newVersion, 'future-apps.json', false);
94
-
95
-		// Apps available on the app store for that version
96
-		$availableApps = array_map(function(array $app) {
97
-			return $app['id'];
98
-		}, $this->appFetcher->get());
99
-
100
-		if (empty($availableApps)) {
101
-			return new DataResponse([
102
-				'appstore_disabled' => false,
103
-				'already_on_latest' => false,
104
-			], Http::STATUS_NOT_FOUND);
105
-		}
106
-
107
-		$missing = array_diff($installedApps, $availableApps);
108
-		$missing = array_map([$this, 'getAppDetails'], $missing);
109
-		sort($missing);
110
-
111
-		$available = array_intersect($installedApps, $availableApps);
112
-		$available = array_map([$this, 'getAppDetails'], $available);
113
-		sort($available);
114
-
115
-		return new DataResponse([
116
-			'missing' => $missing,
117
-			'available' => $available,
118
-		]);
119
-	}
120
-
121
-	/**
122
-	 * Get translated app name
123
-	 *
124
-	 * @param string $appId
125
-	 * @return string[]
126
-	 */
127
-	protected function getAppDetails($appId): array {
128
-		$app = $this->appManager->getAppInfo($appId);
129
-		return [
130
-			'appId' => $appId,
131
-			'appName' => $app['name'] ?? $appId,
132
-		];
133
-	}
36
+    /** @var IConfig */
37
+    protected $config;
38
+
39
+    /** @var IAppManager */
40
+    protected $appManager;
41
+
42
+    /** @var AppFetcher */
43
+    protected $appFetcher;
44
+
45
+    /**
46
+     * @param string $appName
47
+     * @param IRequest $request
48
+     * @param IConfig $config
49
+     * @param IAppManager $appManager
50
+     * @param AppFetcher $appFetcher
51
+     */
52
+    public function __construct($appName,
53
+                                IRequest $request,
54
+                                IConfig $config,
55
+                                IAppManager $appManager,
56
+                                AppFetcher $appFetcher) {
57
+        parent::__construct($appName, $request);
58
+
59
+        $this->config = $config;
60
+        $this->appManager = $appManager;
61
+        $this->appFetcher = $appFetcher;
62
+    }
63
+
64
+    /**
65
+     * @param string $newVersion
66
+     * @return DataResponse
67
+     */
68
+    public function getAppList(string $newVersion): DataResponse {
69
+        if (!$this->config->getSystemValue('appstoreenabled', true)) {
70
+            return new DataResponse([
71
+                'appstore_disabled' => true,
72
+            ], Http::STATUS_NOT_FOUND);
73
+        }
74
+
75
+        // Get list of installed custom apps
76
+        $installedApps = $this->appManager->getInstalledApps();
77
+        $installedApps = array_filter($installedApps, function($app) {
78
+            try {
79
+                $this->appManager->getAppPath($app);
80
+            } catch (AppPathNotFoundException $e) {
81
+                return false;
82
+            }
83
+            return !$this->appManager->isShipped($app);
84
+        });
85
+
86
+        if (empty($installedApps)) {
87
+            return new DataResponse([
88
+                'missing' => [],
89
+                'available' => [],
90
+            ]);
91
+        }
92
+
93
+        $this->appFetcher->setVersion($newVersion, 'future-apps.json', false);
94
+
95
+        // Apps available on the app store for that version
96
+        $availableApps = array_map(function(array $app) {
97
+            return $app['id'];
98
+        }, $this->appFetcher->get());
99
+
100
+        if (empty($availableApps)) {
101
+            return new DataResponse([
102
+                'appstore_disabled' => false,
103
+                'already_on_latest' => false,
104
+            ], Http::STATUS_NOT_FOUND);
105
+        }
106
+
107
+        $missing = array_diff($installedApps, $availableApps);
108
+        $missing = array_map([$this, 'getAppDetails'], $missing);
109
+        sort($missing);
110
+
111
+        $available = array_intersect($installedApps, $availableApps);
112
+        $available = array_map([$this, 'getAppDetails'], $available);
113
+        sort($available);
114
+
115
+        return new DataResponse([
116
+            'missing' => $missing,
117
+            'available' => $available,
118
+        ]);
119
+    }
120
+
121
+    /**
122
+     * Get translated app name
123
+     *
124
+     * @param string $appId
125
+     * @return string[]
126
+     */
127
+    protected function getAppDetails($appId): array {
128
+        $app = $this->appManager->getAppInfo($appId);
129
+        return [
130
+            'appId' => $appId,
131
+            'appName' => $app['name'] ?? $appId,
132
+        ];
133
+    }
134 134
 }
Please login to merge, or discard this patch.
apps/updatenotification/lib/Settings/Admin.php 1 patch
Indentation   +132 added lines, -132 removed lines patch added patch discarded remove patch
@@ -37,136 +37,136 @@
 block discarded – undo
37 37
 use OCP\Util;
38 38
 
39 39
 class Admin implements ISettings {
40
-	/** @var IConfig */
41
-	private $config;
42
-	/** @var UpdateChecker */
43
-	private $updateChecker;
44
-	/** @var IGroupManager */
45
-	private $groupManager;
46
-	/** @var IDateTimeFormatter */
47
-	private $dateTimeFormatter;
48
-	/** @var IFactory */
49
-	private $l10nFactory;
50
-
51
-	public function __construct(
52
-		IConfig $config,
53
-		UpdateChecker $updateChecker,
54
-		IGroupManager $groupManager,
55
-		IDateTimeFormatter $dateTimeFormatter,
56
-		IFactory $l10nFactory
57
-	) {
58
-		$this->config = $config;
59
-		$this->updateChecker = $updateChecker;
60
-		$this->groupManager = $groupManager;
61
-		$this->dateTimeFormatter = $dateTimeFormatter;
62
-		$this->l10nFactory = $l10nFactory;
63
-	}
64
-
65
-	/**
66
-	 * @return TemplateResponse
67
-	 */
68
-	public function getForm(): TemplateResponse {
69
-		$lastUpdateCheckTimestamp = $this->config->getAppValue('core', 'lastupdatedat');
70
-		$lastUpdateCheck = $this->dateTimeFormatter->formatDateTime($lastUpdateCheckTimestamp);
71
-
72
-		$channels = [
73
-			'daily',
74
-			'beta',
75
-			'stable',
76
-			'production',
77
-		];
78
-		$currentChannel = Util::getChannel();
79
-		if ($currentChannel === 'git') {
80
-			$channels[] = 'git';
81
-		}
82
-
83
-		$updateState = $this->updateChecker->getUpdateState();
84
-
85
-		$notifyGroups = json_decode($this->config->getAppValue('updatenotification', 'notify_groups', '["admin"]'), true);
86
-
87
-		$defaultUpdateServerURL = 'https://updates.nextcloud.com/updater_server/';
88
-		$updateServerURL = $this->config->getSystemValue('updater.server.url', $defaultUpdateServerURL);
89
-
90
-		$params = [
91
-			'isNewVersionAvailable' => !empty($updateState['updateAvailable']),
92
-			'isUpdateChecked' => $lastUpdateCheckTimestamp > 0,
93
-			'lastChecked' => $lastUpdateCheck,
94
-			'currentChannel' => $currentChannel,
95
-			'channels' => $channels,
96
-			'newVersion' => empty($updateState['updateVersion']) ? '' : $updateState['updateVersion'],
97
-			'newVersionString' => empty($updateState['updateVersionString']) ? '' : $updateState['updateVersionString'],
98
-			'downloadLink' => empty($updateState['downloadLink']) ? '' : $updateState['downloadLink'],
99
-			'changes' => $this->filterChanges($updateState['changes'] ?? []),
100
-			'updaterEnabled' => empty($updateState['updaterEnabled']) ? false : $updateState['updaterEnabled'],
101
-			'versionIsEol' => empty($updateState['versionIsEol']) ? false : $updateState['versionIsEol'],
102
-			'isDefaultUpdateServerURL' => $updateServerURL === $defaultUpdateServerURL,
103
-			'updateServerURL' => $updateServerURL,
104
-			'notifyGroups' => $this->getSelectedGroups($notifyGroups),
105
-		];
106
-
107
-		$params = [
108
-			'json' => json_encode($params),
109
-		];
110
-
111
-		return new TemplateResponse('updatenotification', 'admin', $params, '');
112
-	}
113
-
114
-	protected function filterChanges(array $changes): array {
115
-		$filtered = [];
116
-		if(isset($changes['changelogURL'])) {
117
-			$filtered['changelogURL'] = $changes['changelogURL'];
118
-		}
119
-		if(!isset($changes['whatsNew'])) {
120
-			return $filtered;
121
-		}
122
-
123
-		$iterator = $this->l10nFactory->getLanguageIterator();
124
-		do {
125
-			$lang = $iterator->current();
126
-			if(isset($changes['whatsNew'][$lang])) {
127
-				$filtered['whatsNew'] = $changes['whatsNew'][$lang];
128
-				return $filtered;
129
-			}
130
-			$iterator->next();
131
-		} while($lang !== 'en' && $iterator->valid());
132
-
133
-		return $filtered;
134
-	}
135
-
136
-	/**
137
-	 * @param array $groupIds
138
-	 * @return array
139
-	 */
140
-	protected function getSelectedGroups(array $groupIds): array {
141
-		$result = [];
142
-		foreach ($groupIds as $groupId) {
143
-			$group = $this->groupManager->get($groupId);
144
-
145
-			if ($group === null) {
146
-				continue;
147
-			}
148
-
149
-			$result[] = ['value' => $group->getGID(), 'label' => $group->getDisplayName()];
150
-		}
151
-
152
-		return $result;
153
-	}
154
-
155
-	/**
156
-	 * @return string the section ID, e.g. 'sharing'
157
-	 */
158
-	public function getSection(): string {
159
-		return 'overview';
160
-	}
161
-
162
-	/**
163
-	 * @return int whether the form should be rather on the top or bottom of
164
-	 * the admin section. The forms are arranged in ascending order of the
165
-	 * priority values. It is required to return a value between 0 and 100.
166
-	 *
167
-	 * E.g.: 70
168
-	 */
169
-	public function getPriority(): int {
170
-		return 11;
171
-	}
40
+    /** @var IConfig */
41
+    private $config;
42
+    /** @var UpdateChecker */
43
+    private $updateChecker;
44
+    /** @var IGroupManager */
45
+    private $groupManager;
46
+    /** @var IDateTimeFormatter */
47
+    private $dateTimeFormatter;
48
+    /** @var IFactory */
49
+    private $l10nFactory;
50
+
51
+    public function __construct(
52
+        IConfig $config,
53
+        UpdateChecker $updateChecker,
54
+        IGroupManager $groupManager,
55
+        IDateTimeFormatter $dateTimeFormatter,
56
+        IFactory $l10nFactory
57
+    ) {
58
+        $this->config = $config;
59
+        $this->updateChecker = $updateChecker;
60
+        $this->groupManager = $groupManager;
61
+        $this->dateTimeFormatter = $dateTimeFormatter;
62
+        $this->l10nFactory = $l10nFactory;
63
+    }
64
+
65
+    /**
66
+     * @return TemplateResponse
67
+     */
68
+    public function getForm(): TemplateResponse {
69
+        $lastUpdateCheckTimestamp = $this->config->getAppValue('core', 'lastupdatedat');
70
+        $lastUpdateCheck = $this->dateTimeFormatter->formatDateTime($lastUpdateCheckTimestamp);
71
+
72
+        $channels = [
73
+            'daily',
74
+            'beta',
75
+            'stable',
76
+            'production',
77
+        ];
78
+        $currentChannel = Util::getChannel();
79
+        if ($currentChannel === 'git') {
80
+            $channels[] = 'git';
81
+        }
82
+
83
+        $updateState = $this->updateChecker->getUpdateState();
84
+
85
+        $notifyGroups = json_decode($this->config->getAppValue('updatenotification', 'notify_groups', '["admin"]'), true);
86
+
87
+        $defaultUpdateServerURL = 'https://updates.nextcloud.com/updater_server/';
88
+        $updateServerURL = $this->config->getSystemValue('updater.server.url', $defaultUpdateServerURL);
89
+
90
+        $params = [
91
+            'isNewVersionAvailable' => !empty($updateState['updateAvailable']),
92
+            'isUpdateChecked' => $lastUpdateCheckTimestamp > 0,
93
+            'lastChecked' => $lastUpdateCheck,
94
+            'currentChannel' => $currentChannel,
95
+            'channels' => $channels,
96
+            'newVersion' => empty($updateState['updateVersion']) ? '' : $updateState['updateVersion'],
97
+            'newVersionString' => empty($updateState['updateVersionString']) ? '' : $updateState['updateVersionString'],
98
+            'downloadLink' => empty($updateState['downloadLink']) ? '' : $updateState['downloadLink'],
99
+            'changes' => $this->filterChanges($updateState['changes'] ?? []),
100
+            'updaterEnabled' => empty($updateState['updaterEnabled']) ? false : $updateState['updaterEnabled'],
101
+            'versionIsEol' => empty($updateState['versionIsEol']) ? false : $updateState['versionIsEol'],
102
+            'isDefaultUpdateServerURL' => $updateServerURL === $defaultUpdateServerURL,
103
+            'updateServerURL' => $updateServerURL,
104
+            'notifyGroups' => $this->getSelectedGroups($notifyGroups),
105
+        ];
106
+
107
+        $params = [
108
+            'json' => json_encode($params),
109
+        ];
110
+
111
+        return new TemplateResponse('updatenotification', 'admin', $params, '');
112
+    }
113
+
114
+    protected function filterChanges(array $changes): array {
115
+        $filtered = [];
116
+        if(isset($changes['changelogURL'])) {
117
+            $filtered['changelogURL'] = $changes['changelogURL'];
118
+        }
119
+        if(!isset($changes['whatsNew'])) {
120
+            return $filtered;
121
+        }
122
+
123
+        $iterator = $this->l10nFactory->getLanguageIterator();
124
+        do {
125
+            $lang = $iterator->current();
126
+            if(isset($changes['whatsNew'][$lang])) {
127
+                $filtered['whatsNew'] = $changes['whatsNew'][$lang];
128
+                return $filtered;
129
+            }
130
+            $iterator->next();
131
+        } while($lang !== 'en' && $iterator->valid());
132
+
133
+        return $filtered;
134
+    }
135
+
136
+    /**
137
+     * @param array $groupIds
138
+     * @return array
139
+     */
140
+    protected function getSelectedGroups(array $groupIds): array {
141
+        $result = [];
142
+        foreach ($groupIds as $groupId) {
143
+            $group = $this->groupManager->get($groupId);
144
+
145
+            if ($group === null) {
146
+                continue;
147
+            }
148
+
149
+            $result[] = ['value' => $group->getGID(), 'label' => $group->getDisplayName()];
150
+        }
151
+
152
+        return $result;
153
+    }
154
+
155
+    /**
156
+     * @return string the section ID, e.g. 'sharing'
157
+     */
158
+    public function getSection(): string {
159
+        return 'overview';
160
+    }
161
+
162
+    /**
163
+     * @return int whether the form should be rather on the top or bottom of
164
+     * the admin section. The forms are arranged in ascending order of the
165
+     * priority values. It is required to return a value between 0 and 100.
166
+     *
167
+     * E.g.: 70
168
+     */
169
+    public function getPriority(): int {
170
+        return 11;
171
+    }
172 172
 }
Please login to merge, or discard this patch.
settings/Controller/AppSettingsController.php 2 patches
Indentation   +504 added lines, -504 removed lines patch added patch discarded remove patch
@@ -58,509 +58,509 @@
 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
-		$params = [];
137
-		$params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
138
-		$params['updateCount'] = count($this->getAppsWithUpdates());
139
-		$params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
140
-		$params['bundles'] = $this->getBundles();
141
-		$this->navigationManager->setActiveEntry('core_apps');
142
-
143
-		$templateResponse = new TemplateResponse('settings', 'settings-vue', ['serverData' => $params]);
144
-		$policy = new ContentSecurityPolicy();
145
-		$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
146
-		$templateResponse->setContentSecurityPolicy($policy);
147
-
148
-		return $templateResponse;
149
-	}
150
-
151
-	private function getAppsWithUpdates() {
152
-		$appClass = new \OC_App();
153
-		$apps = $appClass->listAllApps();
154
-		foreach($apps as $key => $app) {
155
-			$newVersion = $this->installer->isUpdateAvailable($app['id']);
156
-			if($newVersion === false) {
157
-				unset($apps[$key]);
158
-			}
159
-		}
160
-		return $apps;
161
-	}
162
-
163
-	private function getBundles() {
164
-		$result = [];
165
-		$bundles = $this->bundleFetcher->getBundles();
166
-		foreach ($bundles as $bundle) {
167
-			$result[] = [
168
-				'name' => $bundle->getName(),
169
-				'id' => $bundle->getIdentifier(),
170
-				'appIdentifiers' => $bundle->getAppIdentifiers()
171
-			];
172
-		}
173
-		return $result;
174
-
175
-	}
176
-
177
-	/**
178
-	 * Get all available categories
179
-	 *
180
-	 * @return JSONResponse
181
-	 */
182
-	public function listCategories(): JSONResponse {
183
-		return new JSONResponse($this->getAllCategories());
184
-	}
185
-
186
-	private function getAllCategories() {
187
-		$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
188
-
189
-		$formattedCategories = [];
190
-		$categories = $this->categoryFetcher->get();
191
-		foreach($categories as $category) {
192
-			$formattedCategories[] = [
193
-				'id' => $category['id'],
194
-				'ident' => $category['id'],
195
-				'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
196
-			];
197
-		}
198
-
199
-		return $formattedCategories;
200
-	}
201
-
202
-	private function fetchApps() {
203
-		$appClass = new \OC_App();
204
-		$apps = $appClass->listAllApps();
205
-		foreach ($apps as $app) {
206
-			$app['installed'] = true;
207
-			$this->allApps[$app['id']] = $app;
208
-		}
209
-
210
-		$apps = $this->getAppsForCategory('');
211
-		foreach ($apps as $app) {
212
-			$app['appstore'] = true;
213
-			if (!array_key_exists($app['id'], $this->allApps)) {
214
-				$this->allApps[$app['id']] = $app;
215
-			} else {
216
-				$this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
217
-			}
218
-		}
219
-
220
-		// add bundle information
221
-		$bundles = $this->bundleFetcher->getBundles();
222
-		foreach($bundles as $bundle) {
223
-			foreach($bundle->getAppIdentifiers() as $identifier) {
224
-				foreach($this->allApps as &$app) {
225
-					if($app['id'] === $identifier) {
226
-						$app['bundleId'] = $bundle->getIdentifier();
227
-						continue;
228
-					}
229
-				}
230
-			}
231
-		}
232
-	}
233
-
234
-	private function getAllApps() {
235
-		return $this->allApps;
236
-	}
237
-	/**
238
-	 * Get all available apps in a category
239
-	 *
240
-	 * @param string $category
241
-	 * @return JSONResponse
242
-	 * @throws \Exception
243
-	 */
244
-	public function listApps(): JSONResponse {
245
-
246
-		$this->fetchApps();
247
-		$apps = $this->getAllApps();
248
-
249
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
250
-
251
-		// Extend existing app details
252
-		$apps = array_map(function($appData) use ($dependencyAnalyzer) {
253
-			if (isset($appData['appstoreData'])) {
254
-				$appstoreData = $appData['appstoreData'];
255
-				$appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($appstoreData['screenshots'][0]['url']) : '';
256
-				$appData['category'] = $appstoreData['categories'];
257
-			}
258
-
259
-			$newVersion = $this->installer->isUpdateAvailable($appData['id']);
260
-			if($newVersion) {
261
-				$appData['update'] = $newVersion;
262
-			}
263
-
264
-			// fix groups to be an array
265
-			$groups = array();
266
-			if (is_string($appData['groups'])) {
267
-				$groups = json_decode($appData['groups']);
268
-			}
269
-			$appData['groups'] = $groups;
270
-			$appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
271
-
272
-			// fix licence vs license
273
-			if (isset($appData['license']) && !isset($appData['licence'])) {
274
-				$appData['licence'] = $appData['license'];
275
-			}
276
-
277
-			$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
278
-			$ignoreMax = in_array($appData['id'], $ignoreMaxApps);
279
-
280
-			// analyse dependencies
281
-			$missing = $dependencyAnalyzer->analyze($appData, $ignoreMax);
282
-			$appData['canInstall'] = empty($missing);
283
-			$appData['missingDependencies'] = $missing;
284
-
285
-			$appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
286
-			$appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
287
-			$appData['isCompatible'] = $dependencyAnalyzer->isMarkedCompatible($appData);
288
-
289
-			return $appData;
290
-		}, $apps);
291
-
292
-		usort($apps, [$this, 'sortApps']);
293
-
294
-		return new JSONResponse(['apps' => $apps, 'status' => 'success']);
295
-	}
296
-
297
-	/**
298
-	 * Get all apps for a category from the app store
299
-	 *
300
-	 * @param string $requestedCategory
301
-	 * @return array
302
-	 * @throws \Exception
303
-	 */
304
-	private function getAppsForCategory($requestedCategory = ''): array {
305
-		$versionParser = new VersionParser();
306
-		$formattedApps = [];
307
-		$apps = $this->appFetcher->get();
308
-		foreach($apps as $app) {
309
-			// Skip all apps not in the requested category
310
-			if ($requestedCategory !== '') {
311
-				$isInCategory = false;
312
-				foreach($app['categories'] as $category) {
313
-					if($category === $requestedCategory) {
314
-						$isInCategory = true;
315
-					}
316
-				}
317
-				if(!$isInCategory) {
318
-					continue;
319
-				}
320
-			}
321
-
322
-			if (!isset($app['releases'][0]['rawPlatformVersionSpec'])) {
323
-				continue;
324
-			}
325
-			$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
326
-			$nextCloudVersionDependencies = [];
327
-			if($nextCloudVersion->getMinimumVersion() !== '') {
328
-				$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
329
-			}
330
-			if($nextCloudVersion->getMaximumVersion() !== '') {
331
-				$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
332
-			}
333
-			$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
334
-			$existsLocally = \OC_App::getAppPath($app['id']) !== false;
335
-			$phpDependencies = [];
336
-			if($phpVersion->getMinimumVersion() !== '') {
337
-				$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
338
-			}
339
-			if($phpVersion->getMaximumVersion() !== '') {
340
-				$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
341
-			}
342
-			if(isset($app['releases'][0]['minIntSize'])) {
343
-				$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
344
-			}
345
-			$authors = '';
346
-			foreach($app['authors'] as $key => $author) {
347
-				$authors .= $author['name'];
348
-				if($key !== count($app['authors']) - 1) {
349
-					$authors .= ', ';
350
-				}
351
-			}
352
-
353
-			$currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
354
-			$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
355
-			$groups = null;
356
-			if($enabledValue !== 'no' && $enabledValue !== 'yes') {
357
-				$groups = $enabledValue;
358
-			}
359
-
360
-			$currentVersion = '';
361
-			if($this->appManager->isInstalled($app['id'])) {
362
-				$currentVersion = $this->appManager->getAppVersion($app['id']);
363
-			} else {
364
-				$currentLanguage = $app['releases'][0]['version'];
365
-			}
366
-
367
-			$formattedApps[] = [
368
-				'id' => $app['id'],
369
-				'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
370
-				'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
371
-				'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
372
-				'license' => $app['releases'][0]['licenses'],
373
-				'author' => $authors,
374
-				'shipped' => false,
375
-				'version' => $currentVersion,
376
-				'default_enable' => '',
377
-				'types' => [],
378
-				'documentation' => [
379
-					'admin' => $app['adminDocs'],
380
-					'user' => $app['userDocs'],
381
-					'developer' => $app['developerDocs']
382
-				],
383
-				'website' => $app['website'],
384
-				'bugs' => $app['issueTracker'],
385
-				'detailpage' => $app['website'],
386
-				'dependencies' => array_merge(
387
-					$nextCloudVersionDependencies,
388
-					$phpDependencies
389
-				),
390
-				'level' => ($app['isFeatured'] === true) ? 200 : 100,
391
-				'missingMaxOwnCloudVersion' => false,
392
-				'missingMinOwnCloudVersion' => false,
393
-				'canInstall' => true,
394
-				'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
395
-				'score' => $app['ratingOverall'],
396
-				'ratingNumOverall' => $app['ratingNumOverall'],
397
-				'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
398
-				'removable' => $existsLocally,
399
-				'active' => $this->appManager->isEnabledForUser($app['id']),
400
-				'needsDownload' => !$existsLocally,
401
-				'groups' => $groups,
402
-				'fromAppStore' => true,
403
-				'appstoreData' => $app,
404
-			];
405
-		}
406
-
407
-		return $formattedApps;
408
-	}
409
-
410
-	/**
411
-	 * @PasswordConfirmationRequired
412
-	 *
413
-	 * @param string $appId
414
-	 * @param array $groups
415
-	 * @return JSONResponse
416
-	 */
417
-	public function enableApp(string $appId, array $groups = []): JSONResponse {
418
-		return $this->enableApps([$appId], $groups);
419
-	}
420
-
421
-	/**
422
-	 * Enable one or more apps
423
-	 *
424
-	 * apps will be enabled for specific groups only if $groups is defined
425
-	 *
426
-	 * @PasswordConfirmationRequired
427
-	 * @param array $appIds
428
-	 * @param array $groups
429
-	 * @return JSONResponse
430
-	 */
431
-	public function enableApps(array $appIds, array $groups = []): JSONResponse {
432
-		try {
433
-			$updateRequired = false;
434
-
435
-			foreach ($appIds as $appId) {
436
-				$appId = OC_App::cleanAppId($appId);
437
-
438
-				// Check if app is already downloaded
439
-				/** @var Installer $installer */
440
-				$installer = \OC::$server->query(Installer::class);
441
-				$isDownloaded = $installer->isDownloaded($appId);
442
-
443
-				if(!$isDownloaded) {
444
-					$installer->downloadApp($appId);
445
-				}
446
-
447
-				$installer->installApp($appId);
448
-
449
-				if (count($groups) > 0) {
450
-					$this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
451
-				} else {
452
-					$this->appManager->enableApp($appId);
453
-				}
454
-				if (\OC_App::shouldUpgrade($appId)) {
455
-					$updateRequired = true;
456
-				}
457
-			}
458
-			return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
459
-
460
-		} catch (\Exception $e) {
461
-			$this->logger->logException($e);
462
-			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
463
-		}
464
-	}
465
-
466
-	private function getGroupList(array $groups) {
467
-		$groupManager = \OC::$server->getGroupManager();
468
-		$groupsList = [];
469
-		foreach ($groups as $group) {
470
-			$groupItem = $groupManager->get($group);
471
-			if ($groupItem instanceof \OCP\IGroup) {
472
-				$groupsList[] = $groupManager->get($group);
473
-			}
474
-		}
475
-		return $groupsList;
476
-	}
477
-
478
-	/**
479
-	 * @PasswordConfirmationRequired
480
-	 *
481
-	 * @param string $appId
482
-	 * @return JSONResponse
483
-	 */
484
-	public function disableApp(string $appId): JSONResponse {
485
-		return $this->disableApps([$appId]);
486
-	}
487
-
488
-	/**
489
-	 * @PasswordConfirmationRequired
490
-	 *
491
-	 * @param array $appIds
492
-	 * @return JSONResponse
493
-	 */
494
-	public function disableApps(array $appIds): JSONResponse {
495
-		try {
496
-			foreach ($appIds as $appId) {
497
-				$appId = OC_App::cleanAppId($appId);
498
-				$this->appManager->disableApp($appId);
499
-			}
500
-			return new JSONResponse([]);
501
-		} catch (\Exception $e) {
502
-			$this->logger->logException($e);
503
-			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
504
-		}
505
-	}
506
-
507
-	/**
508
-	 * @PasswordConfirmationRequired
509
-	 *
510
-	 * @param string $appId
511
-	 * @return JSONResponse
512
-	 */
513
-	public function uninstallApp(string $appId): JSONResponse {
514
-		$appId = OC_App::cleanAppId($appId);
515
-		$result = $this->installer->removeApp($appId);
516
-		if($result !== false) {
517
-			$this->appManager->clearAppsCache();
518
-			return new JSONResponse(['data' => ['appid' => $appId]]);
519
-		}
520
-		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
521
-	}
522
-
523
-	/**
524
-	 * @param string $appId
525
-	 * @return JSONResponse
526
-	 */
527
-	public function updateApp(string $appId): JSONResponse {
528
-		$appId = OC_App::cleanAppId($appId);
529
-
530
-		$this->config->setSystemValue('maintenance', true);
531
-		try {
532
-			$result = $this->installer->updateAppstoreApp($appId);
533
-			$this->config->setSystemValue('maintenance', false);
534
-		} catch (\Exception $ex) {
535
-			$this->config->setSystemValue('maintenance', false);
536
-			return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
537
-		}
538
-
539
-		if ($result !== false) {
540
-			return new JSONResponse(['data' => ['appid' => $appId]]);
541
-		}
542
-		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
543
-	}
544
-
545
-	private function sortApps($a, $b) {
546
-		$a = (string)$a['name'];
547
-		$b = (string)$b['name'];
548
-		if ($a === $b) {
549
-			return 0;
550
-		}
551
-		return ($a < $b) ? -1 : 1;
552
-	}
553
-
554
-	public function force(string $appId): JSONResponse {
555
-		$appId = OC_App::cleanAppId($appId);
556
-
557
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
558
-		if (!in_array($appId, $ignoreMaxApps, true)) {
559
-			$ignoreMaxApps[] = $appId;
560
-			$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
561
-		}
562
-
563
-		return new JSONResponse();
564
-	}
61
+    /** @var \OCP\IL10N */
62
+    private $l10n;
63
+    /** @var IConfig */
64
+    private $config;
65
+    /** @var INavigationManager */
66
+    private $navigationManager;
67
+    /** @var IAppManager */
68
+    private $appManager;
69
+    /** @var CategoryFetcher */
70
+    private $categoryFetcher;
71
+    /** @var AppFetcher */
72
+    private $appFetcher;
73
+    /** @var IFactory */
74
+    private $l10nFactory;
75
+    /** @var BundleFetcher */
76
+    private $bundleFetcher;
77
+    /** @var Installer */
78
+    private $installer;
79
+    /** @var IURLGenerator */
80
+    private $urlGenerator;
81
+    /** @var ILogger */
82
+    private $logger;
83
+
84
+    /** @var array */
85
+    private $allApps = [];
86
+
87
+    /**
88
+     * @param string $appName
89
+     * @param IRequest $request
90
+     * @param IL10N $l10n
91
+     * @param IConfig $config
92
+     * @param INavigationManager $navigationManager
93
+     * @param IAppManager $appManager
94
+     * @param CategoryFetcher $categoryFetcher
95
+     * @param AppFetcher $appFetcher
96
+     * @param IFactory $l10nFactory
97
+     * @param BundleFetcher $bundleFetcher
98
+     * @param Installer $installer
99
+     * @param IURLGenerator $urlGenerator
100
+     * @param ILogger $logger
101
+     */
102
+    public function __construct(string $appName,
103
+                                IRequest $request,
104
+                                IL10N $l10n,
105
+                                IConfig $config,
106
+                                INavigationManager $navigationManager,
107
+                                IAppManager $appManager,
108
+                                CategoryFetcher $categoryFetcher,
109
+                                AppFetcher $appFetcher,
110
+                                IFactory $l10nFactory,
111
+                                BundleFetcher $bundleFetcher,
112
+                                Installer $installer,
113
+                                IURLGenerator $urlGenerator,
114
+                                ILogger $logger) {
115
+        parent::__construct($appName, $request);
116
+        $this->l10n = $l10n;
117
+        $this->config = $config;
118
+        $this->navigationManager = $navigationManager;
119
+        $this->appManager = $appManager;
120
+        $this->categoryFetcher = $categoryFetcher;
121
+        $this->appFetcher = $appFetcher;
122
+        $this->l10nFactory = $l10nFactory;
123
+        $this->bundleFetcher = $bundleFetcher;
124
+        $this->installer = $installer;
125
+        $this->urlGenerator = $urlGenerator;
126
+        $this->logger = $logger;
127
+    }
128
+
129
+    /**
130
+     * @NoCSRFRequired
131
+     *
132
+     * @return TemplateResponse
133
+     */
134
+    public function viewApps(): TemplateResponse {
135
+        \OC_Util::addScript('settings', 'apps');
136
+        $params = [];
137
+        $params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
138
+        $params['updateCount'] = count($this->getAppsWithUpdates());
139
+        $params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
140
+        $params['bundles'] = $this->getBundles();
141
+        $this->navigationManager->setActiveEntry('core_apps');
142
+
143
+        $templateResponse = new TemplateResponse('settings', 'settings-vue', ['serverData' => $params]);
144
+        $policy = new ContentSecurityPolicy();
145
+        $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
146
+        $templateResponse->setContentSecurityPolicy($policy);
147
+
148
+        return $templateResponse;
149
+    }
150
+
151
+    private function getAppsWithUpdates() {
152
+        $appClass = new \OC_App();
153
+        $apps = $appClass->listAllApps();
154
+        foreach($apps as $key => $app) {
155
+            $newVersion = $this->installer->isUpdateAvailable($app['id']);
156
+            if($newVersion === false) {
157
+                unset($apps[$key]);
158
+            }
159
+        }
160
+        return $apps;
161
+    }
162
+
163
+    private function getBundles() {
164
+        $result = [];
165
+        $bundles = $this->bundleFetcher->getBundles();
166
+        foreach ($bundles as $bundle) {
167
+            $result[] = [
168
+                'name' => $bundle->getName(),
169
+                'id' => $bundle->getIdentifier(),
170
+                'appIdentifiers' => $bundle->getAppIdentifiers()
171
+            ];
172
+        }
173
+        return $result;
174
+
175
+    }
176
+
177
+    /**
178
+     * Get all available categories
179
+     *
180
+     * @return JSONResponse
181
+     */
182
+    public function listCategories(): JSONResponse {
183
+        return new JSONResponse($this->getAllCategories());
184
+    }
185
+
186
+    private function getAllCategories() {
187
+        $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
188
+
189
+        $formattedCategories = [];
190
+        $categories = $this->categoryFetcher->get();
191
+        foreach($categories as $category) {
192
+            $formattedCategories[] = [
193
+                'id' => $category['id'],
194
+                'ident' => $category['id'],
195
+                'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
196
+            ];
197
+        }
198
+
199
+        return $formattedCategories;
200
+    }
201
+
202
+    private function fetchApps() {
203
+        $appClass = new \OC_App();
204
+        $apps = $appClass->listAllApps();
205
+        foreach ($apps as $app) {
206
+            $app['installed'] = true;
207
+            $this->allApps[$app['id']] = $app;
208
+        }
209
+
210
+        $apps = $this->getAppsForCategory('');
211
+        foreach ($apps as $app) {
212
+            $app['appstore'] = true;
213
+            if (!array_key_exists($app['id'], $this->allApps)) {
214
+                $this->allApps[$app['id']] = $app;
215
+            } else {
216
+                $this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
217
+            }
218
+        }
219
+
220
+        // add bundle information
221
+        $bundles = $this->bundleFetcher->getBundles();
222
+        foreach($bundles as $bundle) {
223
+            foreach($bundle->getAppIdentifiers() as $identifier) {
224
+                foreach($this->allApps as &$app) {
225
+                    if($app['id'] === $identifier) {
226
+                        $app['bundleId'] = $bundle->getIdentifier();
227
+                        continue;
228
+                    }
229
+                }
230
+            }
231
+        }
232
+    }
233
+
234
+    private function getAllApps() {
235
+        return $this->allApps;
236
+    }
237
+    /**
238
+     * Get all available apps in a category
239
+     *
240
+     * @param string $category
241
+     * @return JSONResponse
242
+     * @throws \Exception
243
+     */
244
+    public function listApps(): JSONResponse {
245
+
246
+        $this->fetchApps();
247
+        $apps = $this->getAllApps();
248
+
249
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
250
+
251
+        // Extend existing app details
252
+        $apps = array_map(function($appData) use ($dependencyAnalyzer) {
253
+            if (isset($appData['appstoreData'])) {
254
+                $appstoreData = $appData['appstoreData'];
255
+                $appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($appstoreData['screenshots'][0]['url']) : '';
256
+                $appData['category'] = $appstoreData['categories'];
257
+            }
258
+
259
+            $newVersion = $this->installer->isUpdateAvailable($appData['id']);
260
+            if($newVersion) {
261
+                $appData['update'] = $newVersion;
262
+            }
263
+
264
+            // fix groups to be an array
265
+            $groups = array();
266
+            if (is_string($appData['groups'])) {
267
+                $groups = json_decode($appData['groups']);
268
+            }
269
+            $appData['groups'] = $groups;
270
+            $appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
271
+
272
+            // fix licence vs license
273
+            if (isset($appData['license']) && !isset($appData['licence'])) {
274
+                $appData['licence'] = $appData['license'];
275
+            }
276
+
277
+            $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
278
+            $ignoreMax = in_array($appData['id'], $ignoreMaxApps);
279
+
280
+            // analyse dependencies
281
+            $missing = $dependencyAnalyzer->analyze($appData, $ignoreMax);
282
+            $appData['canInstall'] = empty($missing);
283
+            $appData['missingDependencies'] = $missing;
284
+
285
+            $appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
286
+            $appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
287
+            $appData['isCompatible'] = $dependencyAnalyzer->isMarkedCompatible($appData);
288
+
289
+            return $appData;
290
+        }, $apps);
291
+
292
+        usort($apps, [$this, 'sortApps']);
293
+
294
+        return new JSONResponse(['apps' => $apps, 'status' => 'success']);
295
+    }
296
+
297
+    /**
298
+     * Get all apps for a category from the app store
299
+     *
300
+     * @param string $requestedCategory
301
+     * @return array
302
+     * @throws \Exception
303
+     */
304
+    private function getAppsForCategory($requestedCategory = ''): array {
305
+        $versionParser = new VersionParser();
306
+        $formattedApps = [];
307
+        $apps = $this->appFetcher->get();
308
+        foreach($apps as $app) {
309
+            // Skip all apps not in the requested category
310
+            if ($requestedCategory !== '') {
311
+                $isInCategory = false;
312
+                foreach($app['categories'] as $category) {
313
+                    if($category === $requestedCategory) {
314
+                        $isInCategory = true;
315
+                    }
316
+                }
317
+                if(!$isInCategory) {
318
+                    continue;
319
+                }
320
+            }
321
+
322
+            if (!isset($app['releases'][0]['rawPlatformVersionSpec'])) {
323
+                continue;
324
+            }
325
+            $nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
326
+            $nextCloudVersionDependencies = [];
327
+            if($nextCloudVersion->getMinimumVersion() !== '') {
328
+                $nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
329
+            }
330
+            if($nextCloudVersion->getMaximumVersion() !== '') {
331
+                $nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
332
+            }
333
+            $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
334
+            $existsLocally = \OC_App::getAppPath($app['id']) !== false;
335
+            $phpDependencies = [];
336
+            if($phpVersion->getMinimumVersion() !== '') {
337
+                $phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
338
+            }
339
+            if($phpVersion->getMaximumVersion() !== '') {
340
+                $phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
341
+            }
342
+            if(isset($app['releases'][0]['minIntSize'])) {
343
+                $phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
344
+            }
345
+            $authors = '';
346
+            foreach($app['authors'] as $key => $author) {
347
+                $authors .= $author['name'];
348
+                if($key !== count($app['authors']) - 1) {
349
+                    $authors .= ', ';
350
+                }
351
+            }
352
+
353
+            $currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
354
+            $enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
355
+            $groups = null;
356
+            if($enabledValue !== 'no' && $enabledValue !== 'yes') {
357
+                $groups = $enabledValue;
358
+            }
359
+
360
+            $currentVersion = '';
361
+            if($this->appManager->isInstalled($app['id'])) {
362
+                $currentVersion = $this->appManager->getAppVersion($app['id']);
363
+            } else {
364
+                $currentLanguage = $app['releases'][0]['version'];
365
+            }
366
+
367
+            $formattedApps[] = [
368
+                'id' => $app['id'],
369
+                'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
370
+                'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
371
+                'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
372
+                'license' => $app['releases'][0]['licenses'],
373
+                'author' => $authors,
374
+                'shipped' => false,
375
+                'version' => $currentVersion,
376
+                'default_enable' => '',
377
+                'types' => [],
378
+                'documentation' => [
379
+                    'admin' => $app['adminDocs'],
380
+                    'user' => $app['userDocs'],
381
+                    'developer' => $app['developerDocs']
382
+                ],
383
+                'website' => $app['website'],
384
+                'bugs' => $app['issueTracker'],
385
+                'detailpage' => $app['website'],
386
+                'dependencies' => array_merge(
387
+                    $nextCloudVersionDependencies,
388
+                    $phpDependencies
389
+                ),
390
+                'level' => ($app['isFeatured'] === true) ? 200 : 100,
391
+                'missingMaxOwnCloudVersion' => false,
392
+                'missingMinOwnCloudVersion' => false,
393
+                'canInstall' => true,
394
+                'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
395
+                'score' => $app['ratingOverall'],
396
+                'ratingNumOverall' => $app['ratingNumOverall'],
397
+                'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
398
+                'removable' => $existsLocally,
399
+                'active' => $this->appManager->isEnabledForUser($app['id']),
400
+                'needsDownload' => !$existsLocally,
401
+                'groups' => $groups,
402
+                'fromAppStore' => true,
403
+                'appstoreData' => $app,
404
+            ];
405
+        }
406
+
407
+        return $formattedApps;
408
+    }
409
+
410
+    /**
411
+     * @PasswordConfirmationRequired
412
+     *
413
+     * @param string $appId
414
+     * @param array $groups
415
+     * @return JSONResponse
416
+     */
417
+    public function enableApp(string $appId, array $groups = []): JSONResponse {
418
+        return $this->enableApps([$appId], $groups);
419
+    }
420
+
421
+    /**
422
+     * Enable one or more apps
423
+     *
424
+     * apps will be enabled for specific groups only if $groups is defined
425
+     *
426
+     * @PasswordConfirmationRequired
427
+     * @param array $appIds
428
+     * @param array $groups
429
+     * @return JSONResponse
430
+     */
431
+    public function enableApps(array $appIds, array $groups = []): JSONResponse {
432
+        try {
433
+            $updateRequired = false;
434
+
435
+            foreach ($appIds as $appId) {
436
+                $appId = OC_App::cleanAppId($appId);
437
+
438
+                // Check if app is already downloaded
439
+                /** @var Installer $installer */
440
+                $installer = \OC::$server->query(Installer::class);
441
+                $isDownloaded = $installer->isDownloaded($appId);
442
+
443
+                if(!$isDownloaded) {
444
+                    $installer->downloadApp($appId);
445
+                }
446
+
447
+                $installer->installApp($appId);
448
+
449
+                if (count($groups) > 0) {
450
+                    $this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
451
+                } else {
452
+                    $this->appManager->enableApp($appId);
453
+                }
454
+                if (\OC_App::shouldUpgrade($appId)) {
455
+                    $updateRequired = true;
456
+                }
457
+            }
458
+            return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
459
+
460
+        } catch (\Exception $e) {
461
+            $this->logger->logException($e);
462
+            return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
463
+        }
464
+    }
465
+
466
+    private function getGroupList(array $groups) {
467
+        $groupManager = \OC::$server->getGroupManager();
468
+        $groupsList = [];
469
+        foreach ($groups as $group) {
470
+            $groupItem = $groupManager->get($group);
471
+            if ($groupItem instanceof \OCP\IGroup) {
472
+                $groupsList[] = $groupManager->get($group);
473
+            }
474
+        }
475
+        return $groupsList;
476
+    }
477
+
478
+    /**
479
+     * @PasswordConfirmationRequired
480
+     *
481
+     * @param string $appId
482
+     * @return JSONResponse
483
+     */
484
+    public function disableApp(string $appId): JSONResponse {
485
+        return $this->disableApps([$appId]);
486
+    }
487
+
488
+    /**
489
+     * @PasswordConfirmationRequired
490
+     *
491
+     * @param array $appIds
492
+     * @return JSONResponse
493
+     */
494
+    public function disableApps(array $appIds): JSONResponse {
495
+        try {
496
+            foreach ($appIds as $appId) {
497
+                $appId = OC_App::cleanAppId($appId);
498
+                $this->appManager->disableApp($appId);
499
+            }
500
+            return new JSONResponse([]);
501
+        } catch (\Exception $e) {
502
+            $this->logger->logException($e);
503
+            return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
504
+        }
505
+    }
506
+
507
+    /**
508
+     * @PasswordConfirmationRequired
509
+     *
510
+     * @param string $appId
511
+     * @return JSONResponse
512
+     */
513
+    public function uninstallApp(string $appId): JSONResponse {
514
+        $appId = OC_App::cleanAppId($appId);
515
+        $result = $this->installer->removeApp($appId);
516
+        if($result !== false) {
517
+            $this->appManager->clearAppsCache();
518
+            return new JSONResponse(['data' => ['appid' => $appId]]);
519
+        }
520
+        return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
521
+    }
522
+
523
+    /**
524
+     * @param string $appId
525
+     * @return JSONResponse
526
+     */
527
+    public function updateApp(string $appId): JSONResponse {
528
+        $appId = OC_App::cleanAppId($appId);
529
+
530
+        $this->config->setSystemValue('maintenance', true);
531
+        try {
532
+            $result = $this->installer->updateAppstoreApp($appId);
533
+            $this->config->setSystemValue('maintenance', false);
534
+        } catch (\Exception $ex) {
535
+            $this->config->setSystemValue('maintenance', false);
536
+            return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
537
+        }
538
+
539
+        if ($result !== false) {
540
+            return new JSONResponse(['data' => ['appid' => $appId]]);
541
+        }
542
+        return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
543
+    }
544
+
545
+    private function sortApps($a, $b) {
546
+        $a = (string)$a['name'];
547
+        $b = (string)$b['name'];
548
+        if ($a === $b) {
549
+            return 0;
550
+        }
551
+        return ($a < $b) ? -1 : 1;
552
+    }
553
+
554
+    public function force(string $appId): JSONResponse {
555
+        $appId = OC_App::cleanAppId($appId);
556
+
557
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
558
+        if (!in_array($appId, $ignoreMaxApps, true)) {
559
+            $ignoreMaxApps[] = $appId;
560
+            $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
561
+        }
562
+
563
+        return new JSONResponse();
564
+    }
565 565
 
566 566
 }
Please login to merge, or discard this patch.
Spacing   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -151,9 +151,9 @@  discard block
 block discarded – undo
151 151
 	private function getAppsWithUpdates() {
152 152
 		$appClass = new \OC_App();
153 153
 		$apps = $appClass->listAllApps();
154
-		foreach($apps as $key => $app) {
154
+		foreach ($apps as $key => $app) {
155 155
 			$newVersion = $this->installer->isUpdateAvailable($app['id']);
156
-			if($newVersion === false) {
156
+			if ($newVersion === false) {
157 157
 				unset($apps[$key]);
158 158
 			}
159 159
 		}
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
 
189 189
 		$formattedCategories = [];
190 190
 		$categories = $this->categoryFetcher->get();
191
-		foreach($categories as $category) {
191
+		foreach ($categories as $category) {
192 192
 			$formattedCategories[] = [
193 193
 				'id' => $category['id'],
194 194
 				'ident' => $category['id'],
@@ -219,10 +219,10 @@  discard block
 block discarded – undo
219 219
 
220 220
 		// add bundle information
221 221
 		$bundles = $this->bundleFetcher->getBundles();
222
-		foreach($bundles as $bundle) {
223
-			foreach($bundle->getAppIdentifiers() as $identifier) {
224
-				foreach($this->allApps as &$app) {
225
-					if($app['id'] === $identifier) {
222
+		foreach ($bundles as $bundle) {
223
+			foreach ($bundle->getAppIdentifiers() as $identifier) {
224
+				foreach ($this->allApps as &$app) {
225
+					if ($app['id'] === $identifier) {
226 226
 						$app['bundleId'] = $bundle->getIdentifier();
227 227
 						continue;
228 228
 					}
@@ -252,12 +252,12 @@  discard block
 block discarded – undo
252 252
 		$apps = array_map(function($appData) use ($dependencyAnalyzer) {
253 253
 			if (isset($appData['appstoreData'])) {
254 254
 				$appstoreData = $appData['appstoreData'];
255
-				$appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($appstoreData['screenshots'][0]['url']) : '';
255
+				$appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($appstoreData['screenshots'][0]['url']) : '';
256 256
 				$appData['category'] = $appstoreData['categories'];
257 257
 			}
258 258
 
259 259
 			$newVersion = $this->installer->isUpdateAvailable($appData['id']);
260
-			if($newVersion) {
260
+			if ($newVersion) {
261 261
 				$appData['update'] = $newVersion;
262 262
 			}
263 263
 
@@ -305,16 +305,16 @@  discard block
 block discarded – undo
305 305
 		$versionParser = new VersionParser();
306 306
 		$formattedApps = [];
307 307
 		$apps = $this->appFetcher->get();
308
-		foreach($apps as $app) {
308
+		foreach ($apps as $app) {
309 309
 			// Skip all apps not in the requested category
310 310
 			if ($requestedCategory !== '') {
311 311
 				$isInCategory = false;
312
-				foreach($app['categories'] as $category) {
313
-					if($category === $requestedCategory) {
312
+				foreach ($app['categories'] as $category) {
313
+					if ($category === $requestedCategory) {
314 314
 						$isInCategory = true;
315 315
 					}
316 316
 				}
317
-				if(!$isInCategory) {
317
+				if (!$isInCategory) {
318 318
 					continue;
319 319
 				}
320 320
 			}
@@ -324,28 +324,28 @@  discard block
 block discarded – undo
324 324
 			}
325 325
 			$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
326 326
 			$nextCloudVersionDependencies = [];
327
-			if($nextCloudVersion->getMinimumVersion() !== '') {
327
+			if ($nextCloudVersion->getMinimumVersion() !== '') {
328 328
 				$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
329 329
 			}
330
-			if($nextCloudVersion->getMaximumVersion() !== '') {
330
+			if ($nextCloudVersion->getMaximumVersion() !== '') {
331 331
 				$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
332 332
 			}
333 333
 			$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
334 334
 			$existsLocally = \OC_App::getAppPath($app['id']) !== false;
335 335
 			$phpDependencies = [];
336
-			if($phpVersion->getMinimumVersion() !== '') {
336
+			if ($phpVersion->getMinimumVersion() !== '') {
337 337
 				$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
338 338
 			}
339
-			if($phpVersion->getMaximumVersion() !== '') {
339
+			if ($phpVersion->getMaximumVersion() !== '') {
340 340
 				$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
341 341
 			}
342
-			if(isset($app['releases'][0]['minIntSize'])) {
342
+			if (isset($app['releases'][0]['minIntSize'])) {
343 343
 				$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
344 344
 			}
345 345
 			$authors = '';
346
-			foreach($app['authors'] as $key => $author) {
346
+			foreach ($app['authors'] as $key => $author) {
347 347
 				$authors .= $author['name'];
348
-				if($key !== count($app['authors']) - 1) {
348
+				if ($key !== count($app['authors']) - 1) {
349 349
 					$authors .= ', ';
350 350
 				}
351 351
 			}
@@ -353,12 +353,12 @@  discard block
 block discarded – undo
353 353
 			$currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
354 354
 			$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
355 355
 			$groups = null;
356
-			if($enabledValue !== 'no' && $enabledValue !== 'yes') {
356
+			if ($enabledValue !== 'no' && $enabledValue !== 'yes') {
357 357
 				$groups = $enabledValue;
358 358
 			}
359 359
 
360 360
 			$currentVersion = '';
361
-			if($this->appManager->isInstalled($app['id'])) {
361
+			if ($this->appManager->isInstalled($app['id'])) {
362 362
 				$currentVersion = $this->appManager->getAppVersion($app['id']);
363 363
 			} else {
364 364
 				$currentLanguage = $app['releases'][0]['version'];
@@ -440,7 +440,7 @@  discard block
 block discarded – undo
440 440
 				$installer = \OC::$server->query(Installer::class);
441 441
 				$isDownloaded = $installer->isDownloaded($appId);
442 442
 
443
-				if(!$isDownloaded) {
443
+				if (!$isDownloaded) {
444 444
 					$installer->downloadApp($appId);
445 445
 				}
446 446
 
@@ -513,7 +513,7 @@  discard block
 block discarded – undo
513 513
 	public function uninstallApp(string $appId): JSONResponse {
514 514
 		$appId = OC_App::cleanAppId($appId);
515 515
 		$result = $this->installer->removeApp($appId);
516
-		if($result !== false) {
516
+		if ($result !== false) {
517 517
 			$this->appManager->clearAppsCache();
518 518
 			return new JSONResponse(['data' => ['appid' => $appId]]);
519 519
 		}
@@ -543,8 +543,8 @@  discard block
 block discarded – undo
543 543
 	}
544 544
 
545 545
 	private function sortApps($a, $b) {
546
-		$a = (string)$a['name'];
547
-		$b = (string)$b['name'];
546
+		$a = (string) $a['name'];
547
+		$b = (string) $b['name'];
548 548
 		if ($a === $b) {
549 549
 			return 0;
550 550
 		}
Please login to merge, or discard this patch.
settings/routes.php 1 patch
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -38,58 +38,58 @@
 block discarded – undo
38 38
 
39 39
 $application = new Application();
40 40
 $application->registerRoutes($this, [
41
-	'resources' => [
42
-		'AuthSettings' => ['url' => '/settings/personal/authtokens'],
43
-	],
44
-	'routes' => [
45
-		['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'],
46
-		['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'],
47
-		['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'],
48
-		['name' => 'Encryption#startMigration', 'url' => '/settings/admin/startmigration', 'verb' => 'POST'],
41
+    'resources' => [
42
+        'AuthSettings' => ['url' => '/settings/personal/authtokens'],
43
+    ],
44
+    'routes' => [
45
+        ['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'],
46
+        ['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'],
47
+        ['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'],
48
+        ['name' => 'Encryption#startMigration', 'url' => '/settings/admin/startmigration', 'verb' => 'POST'],
49 49
 
50
-		['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'],
51
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'],
52
-		['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
53
-		['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'GET'],
54
-		['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'POST'],
55
-		['name' => 'AppSettings#enableApps', 'url' => '/settings/apps/enable', 'verb' => 'POST'],
56
-		['name' => 'AppSettings#disableApp', 'url' => '/settings/apps/disable/{appId}', 'verb' => 'GET'],
57
-		['name' => 'AppSettings#disableApps', 'url' => '/settings/apps/disable', 'verb' => 'POST'],
58
-		['name' => 'AppSettings#updateApp', 'url' => '/settings/apps/update/{appId}', 'verb' => 'GET'],
59
-		['name' => 'AppSettings#uninstallApp', 'url' => '/settings/apps/uninstall/{appId}', 'verb' => 'GET'],
60
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}', 'verb' => 'GET', 'defaults' => ['category' => '']],
61
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}/{id}', 'verb' => 'GET', 'defaults' => ['category' => '', 'id' => '']],
62
-		['name' => 'AppSettings#force', 'url' => '/settings/apps/force', 'verb' => 'POST'],
50
+        ['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'],
51
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'],
52
+        ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
53
+        ['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'GET'],
54
+        ['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'POST'],
55
+        ['name' => 'AppSettings#enableApps', 'url' => '/settings/apps/enable', 'verb' => 'POST'],
56
+        ['name' => 'AppSettings#disableApp', 'url' => '/settings/apps/disable/{appId}', 'verb' => 'GET'],
57
+        ['name' => 'AppSettings#disableApps', 'url' => '/settings/apps/disable', 'verb' => 'POST'],
58
+        ['name' => 'AppSettings#updateApp', 'url' => '/settings/apps/update/{appId}', 'verb' => 'GET'],
59
+        ['name' => 'AppSettings#uninstallApp', 'url' => '/settings/apps/uninstall/{appId}', 'verb' => 'GET'],
60
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}', 'verb' => 'GET', 'defaults' => ['category' => '']],
61
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}/{id}', 'verb' => 'GET', 'defaults' => ['category' => '', 'id' => '']],
62
+        ['name' => 'AppSettings#force', 'url' => '/settings/apps/force', 'verb' => 'POST'],
63 63
 
64
-		['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'],
65
-		['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
66
-		['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'],
67
-		['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'],
68
-		['name' => 'Users#usersList', 'url' => '/settings/users', 'verb' => 'GET'],
69
-		['name' => 'Users#usersListByGroup', 'url' => '/settings/users/{group}', 'verb' => 'GET', 'requirements' => ['group' => '.+']],
70
-		['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'],
71
-		['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'],
72
-		['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'],
73
-		['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'],
74
-		['name' => 'CheckSetup#getFailedIntegrityCheckFiles', 'url' => '/settings/integrity/failed', 'verb' => 'GET'],
75
-		['name' => 'CheckSetup#rescanFailedIntegrityCheck', 'url' => '/settings/integrity/rescan', 'verb' => 'GET'],
76
-		['name' => 'Certificate#addPersonalRootCertificate', 'url' => '/settings/personal/certificate', 'verb' => 'POST'],
77
-		['name' => 'Certificate#removePersonalRootCertificate', 'url' => '/settings/personal/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
78
-		['name' => 'Certificate#addSystemRootCertificate', 'url' => '/settings/admin/certificate', 'verb' => 'POST'],
79
-		['name' => 'Certificate#removeSystemRootCertificate', 'url' => '/settings/admin/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
80
-		['name' => 'PersonalSettings#index', 'url' => '/settings/user/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'personal-info']],
81
-		['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server']],
82
-		['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET'],
83
-		['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST'],
84
-		['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST'],
85
-		['name' => 'TwoFactorSettings#index', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'GET'],
86
-		['name' => 'TwoFactorSettings#update', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'PUT'],
87
-	]
64
+        ['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'],
65
+        ['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
66
+        ['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'],
67
+        ['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'],
68
+        ['name' => 'Users#usersList', 'url' => '/settings/users', 'verb' => 'GET'],
69
+        ['name' => 'Users#usersListByGroup', 'url' => '/settings/users/{group}', 'verb' => 'GET', 'requirements' => ['group' => '.+']],
70
+        ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'],
71
+        ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'],
72
+        ['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'],
73
+        ['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'],
74
+        ['name' => 'CheckSetup#getFailedIntegrityCheckFiles', 'url' => '/settings/integrity/failed', 'verb' => 'GET'],
75
+        ['name' => 'CheckSetup#rescanFailedIntegrityCheck', 'url' => '/settings/integrity/rescan', 'verb' => 'GET'],
76
+        ['name' => 'Certificate#addPersonalRootCertificate', 'url' => '/settings/personal/certificate', 'verb' => 'POST'],
77
+        ['name' => 'Certificate#removePersonalRootCertificate', 'url' => '/settings/personal/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
78
+        ['name' => 'Certificate#addSystemRootCertificate', 'url' => '/settings/admin/certificate', 'verb' => 'POST'],
79
+        ['name' => 'Certificate#removeSystemRootCertificate', 'url' => '/settings/admin/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
80
+        ['name' => 'PersonalSettings#index', 'url' => '/settings/user/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'personal-info']],
81
+        ['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server']],
82
+        ['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET'],
83
+        ['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST'],
84
+        ['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST'],
85
+        ['name' => 'TwoFactorSettings#index', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'GET'],
86
+        ['name' => 'TwoFactorSettings#update', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'PUT'],
87
+    ]
88 88
 ]);
89 89
 
90 90
 /** @var $this \OCP\Route\IRouter */
91 91
 
92 92
 // Settings pages
93 93
 $this->create('settings_help', '/settings/help')
94
-	->actionInclude('settings/help.php');
94
+    ->actionInclude('settings/help.php');
95 95
 
Please login to merge, or discard this patch.