Completed
Pull Request — master (#8506)
by Morris
52:24 queued 34:56
created
lib/private/Installer.php 2 patches
Indentation   +550 added lines, -550 removed lines patch added patch discarded remove patch
@@ -52,554 +52,554 @@
 block discarded – undo
52 52
  * This class provides the functionality needed to install, update and remove apps
53 53
  */
54 54
 class Installer {
55
-	/** @var AppFetcher */
56
-	private $appFetcher;
57
-	/** @var IClientService */
58
-	private $clientService;
59
-	/** @var ITempManager */
60
-	private $tempManager;
61
-	/** @var ILogger */
62
-	private $logger;
63
-	/** @var IConfig */
64
-	private $config;
65
-	/** @var array - for caching the result of app fetcher */
66
-	private $apps = null;
67
-	/** @var bool|null - for caching the result of the ready status */
68
-	private $isInstanceReadyForUpdates = null;
69
-
70
-	/**
71
-	 * @param AppFetcher $appFetcher
72
-	 * @param IClientService $clientService
73
-	 * @param ITempManager $tempManager
74
-	 * @param ILogger $logger
75
-	 * @param IConfig $config
76
-	 */
77
-	public function __construct(AppFetcher $appFetcher,
78
-								IClientService $clientService,
79
-								ITempManager $tempManager,
80
-								ILogger $logger,
81
-								IConfig $config) {
82
-		$this->appFetcher = $appFetcher;
83
-		$this->clientService = $clientService;
84
-		$this->tempManager = $tempManager;
85
-		$this->logger = $logger;
86
-		$this->config = $config;
87
-	}
88
-
89
-	/**
90
-	 * Installs an app that is located in one of the app folders already
91
-	 *
92
-	 * @param string $appId App to install
93
-	 * @throws \Exception
94
-	 * @return string app ID
95
-	 */
96
-	public function installApp($appId) {
97
-		$app = \OC_App::findAppInDirectories($appId);
98
-		if($app === false) {
99
-			throw new \Exception('App not found in any app directory');
100
-		}
101
-
102
-		$basedir = $app['path'].'/'.$appId;
103
-		$info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
104
-
105
-		$l = \OC::$server->getL10N('core');
106
-
107
-		if(!is_array($info)) {
108
-			throw new \Exception(
109
-				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
110
-					[$appId]
111
-				)
112
-			);
113
-		}
114
-
115
-		$version = implode('.', \OCP\Util::getVersion());
116
-		if (!\OC_App::isAppCompatible($version, $info)) {
117
-			throw new \Exception(
118
-				// TODO $l
119
-				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
120
-					[$info['name']]
121
-				)
122
-			);
123
-		}
124
-
125
-		// check for required dependencies
126
-		\OC_App::checkAppDependencies($this->config, $l, $info);
127
-		\OC_App::registerAutoloading($appId, $basedir);
128
-
129
-		//install the database
130
-		if(is_file($basedir.'/appinfo/database.xml')) {
131
-			if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
132
-				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
133
-			} else {
134
-				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
135
-			}
136
-		} else {
137
-			$ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
138
-			$ms->migrate();
139
-		}
140
-
141
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
142
-
143
-		//run appinfo/install.php
144
-		if(!isset($data['noinstall']) or $data['noinstall']==false) {
145
-			self::includeAppScript($basedir . '/appinfo/install.php');
146
-		}
147
-
148
-		$appData = OC_App::getAppInfo($appId);
149
-		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
150
-
151
-		//set the installed version
152
-		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
153
-		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
154
-
155
-		//set remote/public handlers
156
-		foreach($info['remote'] as $name=>$path) {
157
-			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
158
-		}
159
-		foreach($info['public'] as $name=>$path) {
160
-			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
161
-		}
162
-
163
-		OC_App::setAppTypes($info['id']);
164
-
165
-		return $info['id'];
166
-	}
167
-
168
-	/**
169
-	 * Updates the specified app from the appstore
170
-	 *
171
-	 * @param string $appId
172
-	 * @return bool
173
-	 */
174
-	public function updateAppstoreApp($appId) {
175
-		if($this->isUpdateAvailable($appId)) {
176
-			try {
177
-				$this->downloadApp($appId);
178
-			} catch (\Exception $e) {
179
-				$this->logger->logException($e, [
180
-					'level' => \OCP\Util::ERROR,
181
-					'app' => 'core',
182
-				]);
183
-				return false;
184
-			}
185
-			return OC_App::updateApp($appId);
186
-		}
187
-
188
-		return false;
189
-	}
190
-
191
-	/**
192
-	 * Downloads an app and puts it into the app directory
193
-	 *
194
-	 * @param string $appId
195
-	 *
196
-	 * @throws \Exception If the installation was not successful
197
-	 */
198
-	public function downloadApp($appId) {
199
-		$appId = strtolower($appId);
200
-
201
-		$apps = $this->appFetcher->get();
202
-		foreach($apps as $app) {
203
-			if($app['id'] === $appId) {
204
-				// Load the certificate
205
-				$certificate = new X509();
206
-				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
207
-				$loadedCertificate = $certificate->loadX509($app['certificate']);
208
-
209
-				// Verify if the certificate has been revoked
210
-				$crl = new X509();
211
-				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
212
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
213
-				if($crl->validateSignature() !== true) {
214
-					throw new \Exception('Could not validate CRL signature');
215
-				}
216
-				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
217
-				$revoked = $crl->getRevoked($csn);
218
-				if ($revoked !== false) {
219
-					throw new \Exception(
220
-						sprintf(
221
-							'Certificate "%s" has been revoked',
222
-							$csn
223
-						)
224
-					);
225
-				}
226
-
227
-				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
228
-				if($certificate->validateSignature() !== true) {
229
-					throw new \Exception(
230
-						sprintf(
231
-							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
232
-							$appId
233
-						)
234
-					);
235
-				}
236
-
237
-				// Verify if the certificate is issued for the requested app id
238
-				$certInfo = openssl_x509_parse($app['certificate']);
239
-				if(!isset($certInfo['subject']['CN'])) {
240
-					throw new \Exception(
241
-						sprintf(
242
-							'App with id %s has a cert with no CN',
243
-							$appId
244
-						)
245
-					);
246
-				}
247
-				if($certInfo['subject']['CN'] !== $appId) {
248
-					throw new \Exception(
249
-						sprintf(
250
-							'App with id %s has a cert issued to %s',
251
-							$appId,
252
-							$certInfo['subject']['CN']
253
-						)
254
-					);
255
-				}
256
-
257
-				// Download the release
258
-				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
259
-				$client = $this->clientService->newClient();
260
-				$client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
261
-
262
-				// Check if the signature actually matches the downloaded content
263
-				$certificate = openssl_get_publickey($app['certificate']);
264
-				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
265
-				openssl_free_key($certificate);
266
-
267
-				if($verified === true) {
268
-					// Seems to match, let's proceed
269
-					$extractDir = $this->tempManager->getTemporaryFolder();
270
-					$archive = new TAR($tempFile);
271
-
272
-					if($archive) {
273
-						if (!$archive->extract($extractDir)) {
274
-							throw new \Exception(
275
-								sprintf(
276
-									'Could not extract app %s',
277
-									$appId
278
-								)
279
-							);
280
-						}
281
-						$allFiles = scandir($extractDir);
282
-						$folders = array_diff($allFiles, ['.', '..']);
283
-						$folders = array_values($folders);
284
-
285
-						if(count($folders) > 1) {
286
-							throw new \Exception(
287
-								sprintf(
288
-									'Extracted app %s has more than 1 folder',
289
-									$appId
290
-								)
291
-							);
292
-						}
293
-
294
-						// Check if appinfo/info.xml has the same app ID as well
295
-						$loadEntities = libxml_disable_entity_loader(false);
296
-						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
297
-						libxml_disable_entity_loader($loadEntities);
298
-						if((string)$xml->id !== $appId) {
299
-							throw new \Exception(
300
-								sprintf(
301
-									'App for id %s has a wrong app ID in info.xml: %s',
302
-									$appId,
303
-									(string)$xml->id
304
-								)
305
-							);
306
-						}
307
-
308
-						// Check if the version is lower than before
309
-						$currentVersion = OC_App::getAppVersion($appId);
310
-						$newVersion = (string)$xml->version;
311
-						if(version_compare($currentVersion, $newVersion) === 1) {
312
-							throw new \Exception(
313
-								sprintf(
314
-									'App for id %s has version %s and tried to update to lower version %s',
315
-									$appId,
316
-									$currentVersion,
317
-									$newVersion
318
-								)
319
-							);
320
-						}
321
-
322
-						$baseDir = OC_App::getInstallPath() . '/' . $appId;
323
-						// Remove old app with the ID if existent
324
-						OC_Helper::rmdirr($baseDir);
325
-						// Move to app folder
326
-						if(@mkdir($baseDir)) {
327
-							$extractDir .= '/' . $folders[0];
328
-							OC_Helper::copyr($extractDir, $baseDir);
329
-						}
330
-						OC_Helper::copyr($extractDir, $baseDir);
331
-						OC_Helper::rmdirr($extractDir);
332
-						return;
333
-					} else {
334
-						throw new \Exception(
335
-							sprintf(
336
-								'Could not extract app with ID %s to %s',
337
-								$appId,
338
-								$extractDir
339
-							)
340
-						);
341
-					}
342
-				} else {
343
-					// Signature does not match
344
-					throw new \Exception(
345
-						sprintf(
346
-							'App with id %s has invalid signature',
347
-							$appId
348
-						)
349
-					);
350
-				}
351
-			}
352
-		}
353
-
354
-		throw new \Exception(
355
-			sprintf(
356
-				'Could not download app %s',
357
-				$appId
358
-			)
359
-		);
360
-	}
361
-
362
-	/**
363
-	 * Check if an update for the app is available
364
-	 *
365
-	 * @param string $appId
366
-	 * @return string|false false or the version number of the update
367
-	 */
368
-	public function isUpdateAvailable($appId) {
369
-		if ($this->isInstanceReadyForUpdates === null) {
370
-			$installPath = OC_App::getInstallPath();
371
-			if ($installPath === false || $installPath === null) {
372
-				$this->isInstanceReadyForUpdates = false;
373
-			} else {
374
-				$this->isInstanceReadyForUpdates = true;
375
-			}
376
-		}
377
-
378
-		if ($this->isInstanceReadyForUpdates === false) {
379
-			return false;
380
-		}
381
-
382
-		if ($this->isInstalledFromGit($appId) === true) {
383
-			return false;
384
-		}
385
-
386
-		if ($this->apps === null) {
387
-			$this->apps = $this->appFetcher->get();
388
-		}
389
-
390
-		foreach($this->apps as $app) {
391
-			if($app['id'] === $appId) {
392
-				$currentVersion = OC_App::getAppVersion($appId);
393
-				$newestVersion = $app['releases'][0]['version'];
394
-				if (version_compare($newestVersion, $currentVersion, '>')) {
395
-					return $newestVersion;
396
-				} else {
397
-					return false;
398
-				}
399
-			}
400
-		}
401
-
402
-		return false;
403
-	}
404
-
405
-	/**
406
-	 * Check if app has been installed from git
407
-	 * @param string $name name of the application to remove
408
-	 * @return boolean
409
-	 *
410
-	 * The function will check if the path contains a .git folder
411
-	 */
412
-	private function isInstalledFromGit($appId) {
413
-		$app = \OC_App::findAppInDirectories($appId);
414
-		if($app === false) {
415
-			return false;
416
-		}
417
-		$basedir = $app['path'].'/'.$appId;
418
-		return file_exists($basedir.'/.git/');
419
-	}
420
-
421
-	/**
422
-	 * Check if app is already downloaded
423
-	 * @param string $name name of the application to remove
424
-	 * @return boolean
425
-	 *
426
-	 * The function will check if the app is already downloaded in the apps repository
427
-	 */
428
-	public function isDownloaded($name) {
429
-		foreach(\OC::$APPSROOTS as $dir) {
430
-			$dirToTest  = $dir['path'];
431
-			$dirToTest .= '/';
432
-			$dirToTest .= $name;
433
-			$dirToTest .= '/';
434
-
435
-			if (is_dir($dirToTest)) {
436
-				return true;
437
-			}
438
-		}
439
-
440
-		return false;
441
-	}
442
-
443
-	/**
444
-	 * Removes an app
445
-	 * @param string $appId ID of the application to remove
446
-	 * @return boolean
447
-	 *
448
-	 *
449
-	 * This function works as follows
450
-	 *   -# call uninstall repair steps
451
-	 *   -# removing the files
452
-	 *
453
-	 * The function will not delete preferences, tables and the configuration,
454
-	 * this has to be done by the function oc_app_uninstall().
455
-	 */
456
-	public function removeApp($appId) {
457
-		if($this->isDownloaded( $appId )) {
458
-			if (\OC::$server->getAppManager()->isShipped($appId)) {
459
-				return false;
460
-			}
461
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
462
-			OC_Helper::rmdirr($appDir);
463
-			return true;
464
-		}else{
465
-			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
466
-
467
-			return false;
468
-		}
469
-
470
-	}
471
-
472
-	/**
473
-	 * Installs the app within the bundle and marks the bundle as installed
474
-	 *
475
-	 * @param Bundle $bundle
476
-	 * @throws \Exception If app could not get installed
477
-	 */
478
-	public function installAppBundle(Bundle $bundle) {
479
-		$appIds = $bundle->getAppIdentifiers();
480
-		foreach($appIds as $appId) {
481
-			if(!$this->isDownloaded($appId)) {
482
-				$this->downloadApp($appId);
483
-			}
484
-			$this->installApp($appId);
485
-			$app = new OC_App();
486
-			$app->enable($appId);
487
-		}
488
-		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
489
-		$bundles[] = $bundle->getIdentifier();
490
-		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
491
-	}
492
-
493
-	/**
494
-	 * Installs shipped apps
495
-	 *
496
-	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
497
-	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
498
-	 *                         working ownCloud at the end instead of an aborted update.
499
-	 * @return array Array of error messages (appid => Exception)
500
-	 */
501
-	public static function installShippedApps($softErrors = false) {
502
-		$appManager = \OC::$server->getAppManager();
503
-		$config = \OC::$server->getConfig();
504
-		$errors = [];
505
-		foreach(\OC::$APPSROOTS as $app_dir) {
506
-			if($dir = opendir( $app_dir['path'] )) {
507
-				while( false !== ( $filename = readdir( $dir ))) {
508
-					if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
509
-						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
510
-							if($config->getAppValue($filename, "installed_version", null) === null) {
511
-								$info=OC_App::getAppInfo($filename);
512
-								$enabled = isset($info['default_enable']);
513
-								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
514
-									  && $config->getAppValue($filename, 'enabled') !== 'no') {
515
-									if ($softErrors) {
516
-										try {
517
-											Installer::installShippedApp($filename);
518
-										} catch (HintException $e) {
519
-											if ($e->getPrevious() instanceof TableExistsException) {
520
-												$errors[$filename] = $e;
521
-												continue;
522
-											}
523
-											throw $e;
524
-										}
525
-									} else {
526
-										Installer::installShippedApp($filename);
527
-									}
528
-									$config->setAppValue($filename, 'enabled', 'yes');
529
-								}
530
-							}
531
-						}
532
-					}
533
-				}
534
-				closedir( $dir );
535
-			}
536
-		}
537
-
538
-		return $errors;
539
-	}
540
-
541
-	/**
542
-	 * install an app already placed in the app folder
543
-	 * @param string $app id of the app to install
544
-	 * @return integer
545
-	 */
546
-	public static function installShippedApp($app) {
547
-		//install the database
548
-		$appPath = OC_App::getAppPath($app);
549
-		\OC_App::registerAutoloading($app, $appPath);
550
-
551
-		if(is_file("$appPath/appinfo/database.xml")) {
552
-			try {
553
-				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
554
-			} catch (TableExistsException $e) {
555
-				throw new HintException(
556
-					'Failed to enable app ' . $app,
557
-					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
558
-					0, $e
559
-				);
560
-			}
561
-		} else {
562
-			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
563
-			$ms->migrate();
564
-		}
565
-
566
-		//run appinfo/install.php
567
-		self::includeAppScript("$appPath/appinfo/install.php");
568
-
569
-		$info = OC_App::getAppInfo($app);
570
-		if (is_null($info)) {
571
-			return false;
572
-		}
573
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
574
-
575
-		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
576
-
577
-		$config = \OC::$server->getConfig();
578
-
579
-		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
580
-		if (array_key_exists('ocsid', $info)) {
581
-			$config->setAppValue($app, 'ocsid', $info['ocsid']);
582
-		}
583
-
584
-		//set remote/public handlers
585
-		foreach($info['remote'] as $name=>$path) {
586
-			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
587
-		}
588
-		foreach($info['public'] as $name=>$path) {
589
-			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
590
-		}
591
-
592
-		OC_App::setAppTypes($info['id']);
593
-
594
-		return $info['id'];
595
-	}
596
-
597
-	/**
598
-	 * @param string $script
599
-	 */
600
-	private static function includeAppScript($script) {
601
-		if ( file_exists($script) ){
602
-			include $script;
603
-		}
604
-	}
55
+    /** @var AppFetcher */
56
+    private $appFetcher;
57
+    /** @var IClientService */
58
+    private $clientService;
59
+    /** @var ITempManager */
60
+    private $tempManager;
61
+    /** @var ILogger */
62
+    private $logger;
63
+    /** @var IConfig */
64
+    private $config;
65
+    /** @var array - for caching the result of app fetcher */
66
+    private $apps = null;
67
+    /** @var bool|null - for caching the result of the ready status */
68
+    private $isInstanceReadyForUpdates = null;
69
+
70
+    /**
71
+     * @param AppFetcher $appFetcher
72
+     * @param IClientService $clientService
73
+     * @param ITempManager $tempManager
74
+     * @param ILogger $logger
75
+     * @param IConfig $config
76
+     */
77
+    public function __construct(AppFetcher $appFetcher,
78
+                                IClientService $clientService,
79
+                                ITempManager $tempManager,
80
+                                ILogger $logger,
81
+                                IConfig $config) {
82
+        $this->appFetcher = $appFetcher;
83
+        $this->clientService = $clientService;
84
+        $this->tempManager = $tempManager;
85
+        $this->logger = $logger;
86
+        $this->config = $config;
87
+    }
88
+
89
+    /**
90
+     * Installs an app that is located in one of the app folders already
91
+     *
92
+     * @param string $appId App to install
93
+     * @throws \Exception
94
+     * @return string app ID
95
+     */
96
+    public function installApp($appId) {
97
+        $app = \OC_App::findAppInDirectories($appId);
98
+        if($app === false) {
99
+            throw new \Exception('App not found in any app directory');
100
+        }
101
+
102
+        $basedir = $app['path'].'/'.$appId;
103
+        $info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
104
+
105
+        $l = \OC::$server->getL10N('core');
106
+
107
+        if(!is_array($info)) {
108
+            throw new \Exception(
109
+                $l->t('App "%s" cannot be installed because appinfo file cannot be read.',
110
+                    [$appId]
111
+                )
112
+            );
113
+        }
114
+
115
+        $version = implode('.', \OCP\Util::getVersion());
116
+        if (!\OC_App::isAppCompatible($version, $info)) {
117
+            throw new \Exception(
118
+                // TODO $l
119
+                $l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
120
+                    [$info['name']]
121
+                )
122
+            );
123
+        }
124
+
125
+        // check for required dependencies
126
+        \OC_App::checkAppDependencies($this->config, $l, $info);
127
+        \OC_App::registerAutoloading($appId, $basedir);
128
+
129
+        //install the database
130
+        if(is_file($basedir.'/appinfo/database.xml')) {
131
+            if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
132
+                OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
133
+            } else {
134
+                OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
135
+            }
136
+        } else {
137
+            $ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
138
+            $ms->migrate();
139
+        }
140
+
141
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
142
+
143
+        //run appinfo/install.php
144
+        if(!isset($data['noinstall']) or $data['noinstall']==false) {
145
+            self::includeAppScript($basedir . '/appinfo/install.php');
146
+        }
147
+
148
+        $appData = OC_App::getAppInfo($appId);
149
+        OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
150
+
151
+        //set the installed version
152
+        \OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
153
+        \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
154
+
155
+        //set remote/public handlers
156
+        foreach($info['remote'] as $name=>$path) {
157
+            \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
158
+        }
159
+        foreach($info['public'] as $name=>$path) {
160
+            \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
161
+        }
162
+
163
+        OC_App::setAppTypes($info['id']);
164
+
165
+        return $info['id'];
166
+    }
167
+
168
+    /**
169
+     * Updates the specified app from the appstore
170
+     *
171
+     * @param string $appId
172
+     * @return bool
173
+     */
174
+    public function updateAppstoreApp($appId) {
175
+        if($this->isUpdateAvailable($appId)) {
176
+            try {
177
+                $this->downloadApp($appId);
178
+            } catch (\Exception $e) {
179
+                $this->logger->logException($e, [
180
+                    'level' => \OCP\Util::ERROR,
181
+                    'app' => 'core',
182
+                ]);
183
+                return false;
184
+            }
185
+            return OC_App::updateApp($appId);
186
+        }
187
+
188
+        return false;
189
+    }
190
+
191
+    /**
192
+     * Downloads an app and puts it into the app directory
193
+     *
194
+     * @param string $appId
195
+     *
196
+     * @throws \Exception If the installation was not successful
197
+     */
198
+    public function downloadApp($appId) {
199
+        $appId = strtolower($appId);
200
+
201
+        $apps = $this->appFetcher->get();
202
+        foreach($apps as $app) {
203
+            if($app['id'] === $appId) {
204
+                // Load the certificate
205
+                $certificate = new X509();
206
+                $certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
207
+                $loadedCertificate = $certificate->loadX509($app['certificate']);
208
+
209
+                // Verify if the certificate has been revoked
210
+                $crl = new X509();
211
+                $crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
212
+                $crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
213
+                if($crl->validateSignature() !== true) {
214
+                    throw new \Exception('Could not validate CRL signature');
215
+                }
216
+                $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
217
+                $revoked = $crl->getRevoked($csn);
218
+                if ($revoked !== false) {
219
+                    throw new \Exception(
220
+                        sprintf(
221
+                            'Certificate "%s" has been revoked',
222
+                            $csn
223
+                        )
224
+                    );
225
+                }
226
+
227
+                // Verify if the certificate has been issued by the Nextcloud Code Authority CA
228
+                if($certificate->validateSignature() !== true) {
229
+                    throw new \Exception(
230
+                        sprintf(
231
+                            'App with id %s has a certificate not issued by a trusted Code Signing Authority',
232
+                            $appId
233
+                        )
234
+                    );
235
+                }
236
+
237
+                // Verify if the certificate is issued for the requested app id
238
+                $certInfo = openssl_x509_parse($app['certificate']);
239
+                if(!isset($certInfo['subject']['CN'])) {
240
+                    throw new \Exception(
241
+                        sprintf(
242
+                            'App with id %s has a cert with no CN',
243
+                            $appId
244
+                        )
245
+                    );
246
+                }
247
+                if($certInfo['subject']['CN'] !== $appId) {
248
+                    throw new \Exception(
249
+                        sprintf(
250
+                            'App with id %s has a cert issued to %s',
251
+                            $appId,
252
+                            $certInfo['subject']['CN']
253
+                        )
254
+                    );
255
+                }
256
+
257
+                // Download the release
258
+                $tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
259
+                $client = $this->clientService->newClient();
260
+                $client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
261
+
262
+                // Check if the signature actually matches the downloaded content
263
+                $certificate = openssl_get_publickey($app['certificate']);
264
+                $verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
265
+                openssl_free_key($certificate);
266
+
267
+                if($verified === true) {
268
+                    // Seems to match, let's proceed
269
+                    $extractDir = $this->tempManager->getTemporaryFolder();
270
+                    $archive = new TAR($tempFile);
271
+
272
+                    if($archive) {
273
+                        if (!$archive->extract($extractDir)) {
274
+                            throw new \Exception(
275
+                                sprintf(
276
+                                    'Could not extract app %s',
277
+                                    $appId
278
+                                )
279
+                            );
280
+                        }
281
+                        $allFiles = scandir($extractDir);
282
+                        $folders = array_diff($allFiles, ['.', '..']);
283
+                        $folders = array_values($folders);
284
+
285
+                        if(count($folders) > 1) {
286
+                            throw new \Exception(
287
+                                sprintf(
288
+                                    'Extracted app %s has more than 1 folder',
289
+                                    $appId
290
+                                )
291
+                            );
292
+                        }
293
+
294
+                        // Check if appinfo/info.xml has the same app ID as well
295
+                        $loadEntities = libxml_disable_entity_loader(false);
296
+                        $xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
297
+                        libxml_disable_entity_loader($loadEntities);
298
+                        if((string)$xml->id !== $appId) {
299
+                            throw new \Exception(
300
+                                sprintf(
301
+                                    'App for id %s has a wrong app ID in info.xml: %s',
302
+                                    $appId,
303
+                                    (string)$xml->id
304
+                                )
305
+                            );
306
+                        }
307
+
308
+                        // Check if the version is lower than before
309
+                        $currentVersion = OC_App::getAppVersion($appId);
310
+                        $newVersion = (string)$xml->version;
311
+                        if(version_compare($currentVersion, $newVersion) === 1) {
312
+                            throw new \Exception(
313
+                                sprintf(
314
+                                    'App for id %s has version %s and tried to update to lower version %s',
315
+                                    $appId,
316
+                                    $currentVersion,
317
+                                    $newVersion
318
+                                )
319
+                            );
320
+                        }
321
+
322
+                        $baseDir = OC_App::getInstallPath() . '/' . $appId;
323
+                        // Remove old app with the ID if existent
324
+                        OC_Helper::rmdirr($baseDir);
325
+                        // Move to app folder
326
+                        if(@mkdir($baseDir)) {
327
+                            $extractDir .= '/' . $folders[0];
328
+                            OC_Helper::copyr($extractDir, $baseDir);
329
+                        }
330
+                        OC_Helper::copyr($extractDir, $baseDir);
331
+                        OC_Helper::rmdirr($extractDir);
332
+                        return;
333
+                    } else {
334
+                        throw new \Exception(
335
+                            sprintf(
336
+                                'Could not extract app with ID %s to %s',
337
+                                $appId,
338
+                                $extractDir
339
+                            )
340
+                        );
341
+                    }
342
+                } else {
343
+                    // Signature does not match
344
+                    throw new \Exception(
345
+                        sprintf(
346
+                            'App with id %s has invalid signature',
347
+                            $appId
348
+                        )
349
+                    );
350
+                }
351
+            }
352
+        }
353
+
354
+        throw new \Exception(
355
+            sprintf(
356
+                'Could not download app %s',
357
+                $appId
358
+            )
359
+        );
360
+    }
361
+
362
+    /**
363
+     * Check if an update for the app is available
364
+     *
365
+     * @param string $appId
366
+     * @return string|false false or the version number of the update
367
+     */
368
+    public function isUpdateAvailable($appId) {
369
+        if ($this->isInstanceReadyForUpdates === null) {
370
+            $installPath = OC_App::getInstallPath();
371
+            if ($installPath === false || $installPath === null) {
372
+                $this->isInstanceReadyForUpdates = false;
373
+            } else {
374
+                $this->isInstanceReadyForUpdates = true;
375
+            }
376
+        }
377
+
378
+        if ($this->isInstanceReadyForUpdates === false) {
379
+            return false;
380
+        }
381
+
382
+        if ($this->isInstalledFromGit($appId) === true) {
383
+            return false;
384
+        }
385
+
386
+        if ($this->apps === null) {
387
+            $this->apps = $this->appFetcher->get();
388
+        }
389
+
390
+        foreach($this->apps as $app) {
391
+            if($app['id'] === $appId) {
392
+                $currentVersion = OC_App::getAppVersion($appId);
393
+                $newestVersion = $app['releases'][0]['version'];
394
+                if (version_compare($newestVersion, $currentVersion, '>')) {
395
+                    return $newestVersion;
396
+                } else {
397
+                    return false;
398
+                }
399
+            }
400
+        }
401
+
402
+        return false;
403
+    }
404
+
405
+    /**
406
+     * Check if app has been installed from git
407
+     * @param string $name name of the application to remove
408
+     * @return boolean
409
+     *
410
+     * The function will check if the path contains a .git folder
411
+     */
412
+    private function isInstalledFromGit($appId) {
413
+        $app = \OC_App::findAppInDirectories($appId);
414
+        if($app === false) {
415
+            return false;
416
+        }
417
+        $basedir = $app['path'].'/'.$appId;
418
+        return file_exists($basedir.'/.git/');
419
+    }
420
+
421
+    /**
422
+     * Check if app is already downloaded
423
+     * @param string $name name of the application to remove
424
+     * @return boolean
425
+     *
426
+     * The function will check if the app is already downloaded in the apps repository
427
+     */
428
+    public function isDownloaded($name) {
429
+        foreach(\OC::$APPSROOTS as $dir) {
430
+            $dirToTest  = $dir['path'];
431
+            $dirToTest .= '/';
432
+            $dirToTest .= $name;
433
+            $dirToTest .= '/';
434
+
435
+            if (is_dir($dirToTest)) {
436
+                return true;
437
+            }
438
+        }
439
+
440
+        return false;
441
+    }
442
+
443
+    /**
444
+     * Removes an app
445
+     * @param string $appId ID of the application to remove
446
+     * @return boolean
447
+     *
448
+     *
449
+     * This function works as follows
450
+     *   -# call uninstall repair steps
451
+     *   -# removing the files
452
+     *
453
+     * The function will not delete preferences, tables and the configuration,
454
+     * this has to be done by the function oc_app_uninstall().
455
+     */
456
+    public function removeApp($appId) {
457
+        if($this->isDownloaded( $appId )) {
458
+            if (\OC::$server->getAppManager()->isShipped($appId)) {
459
+                return false;
460
+            }
461
+            $appDir = OC_App::getInstallPath() . '/' . $appId;
462
+            OC_Helper::rmdirr($appDir);
463
+            return true;
464
+        }else{
465
+            \OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
466
+
467
+            return false;
468
+        }
469
+
470
+    }
471
+
472
+    /**
473
+     * Installs the app within the bundle and marks the bundle as installed
474
+     *
475
+     * @param Bundle $bundle
476
+     * @throws \Exception If app could not get installed
477
+     */
478
+    public function installAppBundle(Bundle $bundle) {
479
+        $appIds = $bundle->getAppIdentifiers();
480
+        foreach($appIds as $appId) {
481
+            if(!$this->isDownloaded($appId)) {
482
+                $this->downloadApp($appId);
483
+            }
484
+            $this->installApp($appId);
485
+            $app = new OC_App();
486
+            $app->enable($appId);
487
+        }
488
+        $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
489
+        $bundles[] = $bundle->getIdentifier();
490
+        $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
491
+    }
492
+
493
+    /**
494
+     * Installs shipped apps
495
+     *
496
+     * This function installs all apps found in the 'apps' directory that should be enabled by default;
497
+     * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
498
+     *                         working ownCloud at the end instead of an aborted update.
499
+     * @return array Array of error messages (appid => Exception)
500
+     */
501
+    public static function installShippedApps($softErrors = false) {
502
+        $appManager = \OC::$server->getAppManager();
503
+        $config = \OC::$server->getConfig();
504
+        $errors = [];
505
+        foreach(\OC::$APPSROOTS as $app_dir) {
506
+            if($dir = opendir( $app_dir['path'] )) {
507
+                while( false !== ( $filename = readdir( $dir ))) {
508
+                    if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
509
+                        if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
510
+                            if($config->getAppValue($filename, "installed_version", null) === null) {
511
+                                $info=OC_App::getAppInfo($filename);
512
+                                $enabled = isset($info['default_enable']);
513
+                                if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
514
+                                      && $config->getAppValue($filename, 'enabled') !== 'no') {
515
+                                    if ($softErrors) {
516
+                                        try {
517
+                                            Installer::installShippedApp($filename);
518
+                                        } catch (HintException $e) {
519
+                                            if ($e->getPrevious() instanceof TableExistsException) {
520
+                                                $errors[$filename] = $e;
521
+                                                continue;
522
+                                            }
523
+                                            throw $e;
524
+                                        }
525
+                                    } else {
526
+                                        Installer::installShippedApp($filename);
527
+                                    }
528
+                                    $config->setAppValue($filename, 'enabled', 'yes');
529
+                                }
530
+                            }
531
+                        }
532
+                    }
533
+                }
534
+                closedir( $dir );
535
+            }
536
+        }
537
+
538
+        return $errors;
539
+    }
540
+
541
+    /**
542
+     * install an app already placed in the app folder
543
+     * @param string $app id of the app to install
544
+     * @return integer
545
+     */
546
+    public static function installShippedApp($app) {
547
+        //install the database
548
+        $appPath = OC_App::getAppPath($app);
549
+        \OC_App::registerAutoloading($app, $appPath);
550
+
551
+        if(is_file("$appPath/appinfo/database.xml")) {
552
+            try {
553
+                OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
554
+            } catch (TableExistsException $e) {
555
+                throw new HintException(
556
+                    'Failed to enable app ' . $app,
557
+                    'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
558
+                    0, $e
559
+                );
560
+            }
561
+        } else {
562
+            $ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
563
+            $ms->migrate();
564
+        }
565
+
566
+        //run appinfo/install.php
567
+        self::includeAppScript("$appPath/appinfo/install.php");
568
+
569
+        $info = OC_App::getAppInfo($app);
570
+        if (is_null($info)) {
571
+            return false;
572
+        }
573
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
574
+
575
+        OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
576
+
577
+        $config = \OC::$server->getConfig();
578
+
579
+        $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
580
+        if (array_key_exists('ocsid', $info)) {
581
+            $config->setAppValue($app, 'ocsid', $info['ocsid']);
582
+        }
583
+
584
+        //set remote/public handlers
585
+        foreach($info['remote'] as $name=>$path) {
586
+            $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
587
+        }
588
+        foreach($info['public'] as $name=>$path) {
589
+            $config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
590
+        }
591
+
592
+        OC_App::setAppTypes($info['id']);
593
+
594
+        return $info['id'];
595
+    }
596
+
597
+    /**
598
+     * @param string $script
599
+     */
600
+    private static function includeAppScript($script) {
601
+        if ( file_exists($script) ){
602
+            include $script;
603
+        }
604
+    }
605 605
 }
Please login to merge, or discard this patch.
Spacing   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -95,7 +95,7 @@  discard block
 block discarded – undo
95 95
 	 */
96 96
 	public function installApp($appId) {
97 97
 		$app = \OC_App::findAppInDirectories($appId);
98
-		if($app === false) {
98
+		if ($app === false) {
99 99
 			throw new \Exception('App not found in any app directory');
100 100
 		}
101 101
 
@@ -104,7 +104,7 @@  discard block
 block discarded – undo
104 104
 
105 105
 		$l = \OC::$server->getL10N('core');
106 106
 
107
-		if(!is_array($info)) {
107
+		if (!is_array($info)) {
108 108
 			throw new \Exception(
109 109
 				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
110 110
 					[$appId]
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
 		\OC_App::registerAutoloading($appId, $basedir);
128 128
 
129 129
 		//install the database
130
-		if(is_file($basedir.'/appinfo/database.xml')) {
130
+		if (is_file($basedir.'/appinfo/database.xml')) {
131 131
 			if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
132 132
 				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
133 133
 			} else {
@@ -141,8 +141,8 @@  discard block
 block discarded – undo
141 141
 		\OC_App::setupBackgroundJobs($info['background-jobs']);
142 142
 
143 143
 		//run appinfo/install.php
144
-		if(!isset($data['noinstall']) or $data['noinstall']==false) {
145
-			self::includeAppScript($basedir . '/appinfo/install.php');
144
+		if (!isset($data['noinstall']) or $data['noinstall'] == false) {
145
+			self::includeAppScript($basedir.'/appinfo/install.php');
146 146
 		}
147 147
 
148 148
 		$appData = OC_App::getAppInfo($appId);
@@ -153,10 +153,10 @@  discard block
 block discarded – undo
153 153
 		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
154 154
 
155 155
 		//set remote/public handlers
156
-		foreach($info['remote'] as $name=>$path) {
156
+		foreach ($info['remote'] as $name=>$path) {
157 157
 			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
158 158
 		}
159
-		foreach($info['public'] as $name=>$path) {
159
+		foreach ($info['public'] as $name=>$path) {
160 160
 			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
161 161
 		}
162 162
 
@@ -172,7 +172,7 @@  discard block
 block discarded – undo
172 172
 	 * @return bool
173 173
 	 */
174 174
 	public function updateAppstoreApp($appId) {
175
-		if($this->isUpdateAvailable($appId)) {
175
+		if ($this->isUpdateAvailable($appId)) {
176 176
 			try {
177 177
 				$this->downloadApp($appId);
178 178
 			} catch (\Exception $e) {
@@ -199,18 +199,18 @@  discard block
 block discarded – undo
199 199
 		$appId = strtolower($appId);
200 200
 
201 201
 		$apps = $this->appFetcher->get();
202
-		foreach($apps as $app) {
203
-			if($app['id'] === $appId) {
202
+		foreach ($apps as $app) {
203
+			if ($app['id'] === $appId) {
204 204
 				// Load the certificate
205 205
 				$certificate = new X509();
206
-				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
206
+				$certificate->loadCA(file_get_contents(__DIR__.'/../../resources/codesigning/root.crt'));
207 207
 				$loadedCertificate = $certificate->loadX509($app['certificate']);
208 208
 
209 209
 				// Verify if the certificate has been revoked
210 210
 				$crl = new X509();
211
-				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
212
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
213
-				if($crl->validateSignature() !== true) {
211
+				$crl->loadCA(file_get_contents(__DIR__.'/../../resources/codesigning/root.crt'));
212
+				$crl->loadCRL(file_get_contents(__DIR__.'/../../resources/codesigning/root.crl'));
213
+				if ($crl->validateSignature() !== true) {
214 214
 					throw new \Exception('Could not validate CRL signature');
215 215
 				}
216 216
 				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
@@ -225,7 +225,7 @@  discard block
 block discarded – undo
225 225
 				}
226 226
 
227 227
 				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
228
-				if($certificate->validateSignature() !== true) {
228
+				if ($certificate->validateSignature() !== true) {
229 229
 					throw new \Exception(
230 230
 						sprintf(
231 231
 							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
@@ -236,7 +236,7 @@  discard block
 block discarded – undo
236 236
 
237 237
 				// Verify if the certificate is issued for the requested app id
238 238
 				$certInfo = openssl_x509_parse($app['certificate']);
239
-				if(!isset($certInfo['subject']['CN'])) {
239
+				if (!isset($certInfo['subject']['CN'])) {
240 240
 					throw new \Exception(
241 241
 						sprintf(
242 242
 							'App with id %s has a cert with no CN',
@@ -244,7 +244,7 @@  discard block
 block discarded – undo
244 244
 						)
245 245
 					);
246 246
 				}
247
-				if($certInfo['subject']['CN'] !== $appId) {
247
+				if ($certInfo['subject']['CN'] !== $appId) {
248 248
 					throw new \Exception(
249 249
 						sprintf(
250 250
 							'App with id %s has a cert issued to %s',
@@ -261,15 +261,15 @@  discard block
 block discarded – undo
261 261
 
262 262
 				// Check if the signature actually matches the downloaded content
263 263
 				$certificate = openssl_get_publickey($app['certificate']);
264
-				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
264
+				$verified = (bool) openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
265 265
 				openssl_free_key($certificate);
266 266
 
267
-				if($verified === true) {
267
+				if ($verified === true) {
268 268
 					// Seems to match, let's proceed
269 269
 					$extractDir = $this->tempManager->getTemporaryFolder();
270 270
 					$archive = new TAR($tempFile);
271 271
 
272
-					if($archive) {
272
+					if ($archive) {
273 273
 						if (!$archive->extract($extractDir)) {
274 274
 							throw new \Exception(
275 275
 								sprintf(
@@ -282,7 +282,7 @@  discard block
 block discarded – undo
282 282
 						$folders = array_diff($allFiles, ['.', '..']);
283 283
 						$folders = array_values($folders);
284 284
 
285
-						if(count($folders) > 1) {
285
+						if (count($folders) > 1) {
286 286
 							throw new \Exception(
287 287
 								sprintf(
288 288
 									'Extracted app %s has more than 1 folder',
@@ -293,22 +293,22 @@  discard block
 block discarded – undo
293 293
 
294 294
 						// Check if appinfo/info.xml has the same app ID as well
295 295
 						$loadEntities = libxml_disable_entity_loader(false);
296
-						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
296
+						$xml = simplexml_load_file($extractDir.'/'.$folders[0].'/appinfo/info.xml');
297 297
 						libxml_disable_entity_loader($loadEntities);
298
-						if((string)$xml->id !== $appId) {
298
+						if ((string) $xml->id !== $appId) {
299 299
 							throw new \Exception(
300 300
 								sprintf(
301 301
 									'App for id %s has a wrong app ID in info.xml: %s',
302 302
 									$appId,
303
-									(string)$xml->id
303
+									(string) $xml->id
304 304
 								)
305 305
 							);
306 306
 						}
307 307
 
308 308
 						// Check if the version is lower than before
309 309
 						$currentVersion = OC_App::getAppVersion($appId);
310
-						$newVersion = (string)$xml->version;
311
-						if(version_compare($currentVersion, $newVersion) === 1) {
310
+						$newVersion = (string) $xml->version;
311
+						if (version_compare($currentVersion, $newVersion) === 1) {
312 312
 							throw new \Exception(
313 313
 								sprintf(
314 314
 									'App for id %s has version %s and tried to update to lower version %s',
@@ -319,12 +319,12 @@  discard block
 block discarded – undo
319 319
 							);
320 320
 						}
321 321
 
322
-						$baseDir = OC_App::getInstallPath() . '/' . $appId;
322
+						$baseDir = OC_App::getInstallPath().'/'.$appId;
323 323
 						// Remove old app with the ID if existent
324 324
 						OC_Helper::rmdirr($baseDir);
325 325
 						// Move to app folder
326
-						if(@mkdir($baseDir)) {
327
-							$extractDir .= '/' . $folders[0];
326
+						if (@mkdir($baseDir)) {
327
+							$extractDir .= '/'.$folders[0];
328 328
 							OC_Helper::copyr($extractDir, $baseDir);
329 329
 						}
330 330
 						OC_Helper::copyr($extractDir, $baseDir);
@@ -387,8 +387,8 @@  discard block
 block discarded – undo
387 387
 			$this->apps = $this->appFetcher->get();
388 388
 		}
389 389
 
390
-		foreach($this->apps as $app) {
391
-			if($app['id'] === $appId) {
390
+		foreach ($this->apps as $app) {
391
+			if ($app['id'] === $appId) {
392 392
 				$currentVersion = OC_App::getAppVersion($appId);
393 393
 				$newestVersion = $app['releases'][0]['version'];
394 394
 				if (version_compare($newestVersion, $currentVersion, '>')) {
@@ -411,7 +411,7 @@  discard block
 block discarded – undo
411 411
 	 */
412 412
 	private function isInstalledFromGit($appId) {
413 413
 		$app = \OC_App::findAppInDirectories($appId);
414
-		if($app === false) {
414
+		if ($app === false) {
415 415
 			return false;
416 416
 		}
417 417
 		$basedir = $app['path'].'/'.$appId;
@@ -426,7 +426,7 @@  discard block
 block discarded – undo
426 426
 	 * The function will check if the app is already downloaded in the apps repository
427 427
 	 */
428 428
 	public function isDownloaded($name) {
429
-		foreach(\OC::$APPSROOTS as $dir) {
429
+		foreach (\OC::$APPSROOTS as $dir) {
430 430
 			$dirToTest  = $dir['path'];
431 431
 			$dirToTest .= '/';
432 432
 			$dirToTest .= $name;
@@ -454,14 +454,14 @@  discard block
 block discarded – undo
454 454
 	 * this has to be done by the function oc_app_uninstall().
455 455
 	 */
456 456
 	public function removeApp($appId) {
457
-		if($this->isDownloaded( $appId )) {
457
+		if ($this->isDownloaded($appId)) {
458 458
 			if (\OC::$server->getAppManager()->isShipped($appId)) {
459 459
 				return false;
460 460
 			}
461
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
461
+			$appDir = OC_App::getInstallPath().'/'.$appId;
462 462
 			OC_Helper::rmdirr($appDir);
463 463
 			return true;
464
-		}else{
464
+		} else {
465 465
 			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
466 466
 
467 467
 			return false;
@@ -477,8 +477,8 @@  discard block
 block discarded – undo
477 477
 	 */
478 478
 	public function installAppBundle(Bundle $bundle) {
479 479
 		$appIds = $bundle->getAppIdentifiers();
480
-		foreach($appIds as $appId) {
481
-			if(!$this->isDownloaded($appId)) {
480
+		foreach ($appIds as $appId) {
481
+			if (!$this->isDownloaded($appId)) {
482 482
 				$this->downloadApp($appId);
483 483
 			}
484 484
 			$this->installApp($appId);
@@ -502,13 +502,13 @@  discard block
 block discarded – undo
502 502
 		$appManager = \OC::$server->getAppManager();
503 503
 		$config = \OC::$server->getConfig();
504 504
 		$errors = [];
505
-		foreach(\OC::$APPSROOTS as $app_dir) {
506
-			if($dir = opendir( $app_dir['path'] )) {
507
-				while( false !== ( $filename = readdir( $dir ))) {
508
-					if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
509
-						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
510
-							if($config->getAppValue($filename, "installed_version", null) === null) {
511
-								$info=OC_App::getAppInfo($filename);
505
+		foreach (\OC::$APPSROOTS as $app_dir) {
506
+			if ($dir = opendir($app_dir['path'])) {
507
+				while (false !== ($filename = readdir($dir))) {
508
+					if ($filename[0] !== '.' and is_dir($app_dir['path']."/$filename")) {
509
+						if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) {
510
+							if ($config->getAppValue($filename, "installed_version", null) === null) {
511
+								$info = OC_App::getAppInfo($filename);
512 512
 								$enabled = isset($info['default_enable']);
513 513
 								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
514 514
 									  && $config->getAppValue($filename, 'enabled') !== 'no') {
@@ -531,7 +531,7 @@  discard block
 block discarded – undo
531 531
 						}
532 532
 					}
533 533
 				}
534
-				closedir( $dir );
534
+				closedir($dir);
535 535
 			}
536 536
 		}
537 537
 
@@ -548,12 +548,12 @@  discard block
 block discarded – undo
548 548
 		$appPath = OC_App::getAppPath($app);
549 549
 		\OC_App::registerAutoloading($app, $appPath);
550 550
 
551
-		if(is_file("$appPath/appinfo/database.xml")) {
551
+		if (is_file("$appPath/appinfo/database.xml")) {
552 552
 			try {
553 553
 				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
554 554
 			} catch (TableExistsException $e) {
555 555
 				throw new HintException(
556
-					'Failed to enable app ' . $app,
556
+					'Failed to enable app '.$app,
557 557
 					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
558 558
 					0, $e
559 559
 				);
@@ -582,10 +582,10 @@  discard block
 block discarded – undo
582 582
 		}
583 583
 
584 584
 		//set remote/public handlers
585
-		foreach($info['remote'] as $name=>$path) {
585
+		foreach ($info['remote'] as $name=>$path) {
586 586
 			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
587 587
 		}
588
-		foreach($info['public'] as $name=>$path) {
588
+		foreach ($info['public'] as $name=>$path) {
589 589
 			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
590 590
 		}
591 591
 
@@ -598,7 +598,7 @@  discard block
 block discarded – undo
598 598
 	 * @param string $script
599 599
 	 */
600 600
 	private static function includeAppScript($script) {
601
-		if ( file_exists($script) ){
601
+		if (file_exists($script)) {
602 602
 			include $script;
603 603
 		}
604 604
 	}
Please login to merge, or discard this patch.
core/Command/App/Install.php 1 patch
Indentation   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -31,55 +31,55 @@
 block discarded – undo
31 31
 
32 32
 class Install extends Command {
33 33
 
34
-	protected function configure() {
35
-		$this
36
-			->setName('app:install')
37
-			->setDescription('install an app')
38
-			->addArgument(
39
-				'app-id',
40
-				InputArgument::REQUIRED,
41
-				'install the specified app'
42
-			)
43
-			->addOption(
44
-				'keep-disabled',
45
-				null,
46
-				InputOption::VALUE_NONE,
47
-				'don\'t enable the app afterwards'
48
-			)
49
-		;
50
-	}
34
+    protected function configure() {
35
+        $this
36
+            ->setName('app:install')
37
+            ->setDescription('install an app')
38
+            ->addArgument(
39
+                'app-id',
40
+                InputArgument::REQUIRED,
41
+                'install the specified app'
42
+            )
43
+            ->addOption(
44
+                'keep-disabled',
45
+                null,
46
+                InputOption::VALUE_NONE,
47
+                'don\'t enable the app afterwards'
48
+            )
49
+        ;
50
+    }
51 51
 
52
-	protected function execute(InputInterface $input, OutputInterface $output) {
53
-		$appId = $input->getArgument('app-id');
52
+    protected function execute(InputInterface $input, OutputInterface $output) {
53
+        $appId = $input->getArgument('app-id');
54 54
 
55
-		if (\OC_App::getAppPath($appId)) {
56
-			$output->writeln($appId . ' already installed');
57
-			return 1;
58
-		}
55
+        if (\OC_App::getAppPath($appId)) {
56
+            $output->writeln($appId . ' already installed');
57
+            return 1;
58
+        }
59 59
 
60
-		try {
61
-			/** @var Installer $installer */
62
-			$installer = \OC::$server->query(Installer::class);
63
-			$installer->downloadApp($appId);
64
-			$result = $installer->installApp($appId);
65
-		} catch(\Exception $e) {
66
-			$output->writeln('Error: ' . $e->getMessage());
67
-			return 1;
68
-		}
60
+        try {
61
+            /** @var Installer $installer */
62
+            $installer = \OC::$server->query(Installer::class);
63
+            $installer->downloadApp($appId);
64
+            $result = $installer->installApp($appId);
65
+        } catch(\Exception $e) {
66
+            $output->writeln('Error: ' . $e->getMessage());
67
+            return 1;
68
+        }
69 69
 
70
-		if($result === false) {
71
-			$output->writeln($appId . ' couldn\'t be installed');
72
-			return 1;
73
-		}
70
+        if($result === false) {
71
+            $output->writeln($appId . ' couldn\'t be installed');
72
+            return 1;
73
+        }
74 74
 
75
-		$output->writeln($appId . ' installed');
75
+        $output->writeln($appId . ' installed');
76 76
 
77
-		if (!$input->getOption('keep-disabled')) {
78
-			$appClass = new \OC_App();
79
-			$appClass->enable($appId);
80
-			$output->writeln($appId . ' enabled');
81
-		}
77
+        if (!$input->getOption('keep-disabled')) {
78
+            $appClass = new \OC_App();
79
+            $appClass->enable($appId);
80
+            $output->writeln($appId . ' enabled');
81
+        }
82 82
 
83
-		return 0;
84
-	}
83
+        return 0;
84
+    }
85 85
 }
Please login to merge, or discard this patch.
lib/private/legacy/app.php 1 patch
Indentation   +1029 added lines, -1029 removed lines patch added patch discarded remove patch
@@ -63,1033 +63,1033 @@
 block discarded – undo
63 63
  * upgrading and removing apps.
64 64
  */
65 65
 class OC_App {
66
-	static private $adminForms = [];
67
-	static private $personalForms = [];
68
-	static private $appTypes = [];
69
-	static private $loadedApps = [];
70
-	static private $altLogin = [];
71
-	static private $alreadyRegistered = [];
72
-	const officialApp = 200;
73
-
74
-	/**
75
-	 * clean the appId
76
-	 *
77
-	 * @param string $app AppId that needs to be cleaned
78
-	 * @return string
79
-	 */
80
-	public static function cleanAppId(string $app): string {
81
-		return str_replace(array('\0', '/', '\\', '..'), '', $app);
82
-	}
83
-
84
-	/**
85
-	 * Check if an app is loaded
86
-	 *
87
-	 * @param string $app
88
-	 * @return bool
89
-	 */
90
-	public static function isAppLoaded(string $app): bool {
91
-		return in_array($app, self::$loadedApps, true);
92
-	}
93
-
94
-	/**
95
-	 * loads all apps
96
-	 *
97
-	 * @param string[] $types
98
-	 * @return bool
99
-	 *
100
-	 * This function walks through the ownCloud directory and loads all apps
101
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
102
-	 * exists.
103
-	 *
104
-	 * if $types is set to non-empty array, only apps of those types will be loaded
105
-	 */
106
-	public static function loadApps(array $types = []): bool {
107
-		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
108
-			return false;
109
-		}
110
-		// Load the enabled apps here
111
-		$apps = self::getEnabledApps();
112
-
113
-		// Add each apps' folder as allowed class path
114
-		foreach($apps as $app) {
115
-			$path = self::getAppPath($app);
116
-			if($path !== false) {
117
-				self::registerAutoloading($app, $path);
118
-			}
119
-		}
120
-
121
-		// prevent app.php from printing output
122
-		ob_start();
123
-		foreach ($apps as $app) {
124
-			if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
125
-				self::loadApp($app);
126
-			}
127
-		}
128
-		ob_end_clean();
129
-
130
-		return true;
131
-	}
132
-
133
-	/**
134
-	 * load a single app
135
-	 *
136
-	 * @param string $app
137
-	 * @throws Exception
138
-	 */
139
-	public static function loadApp(string $app) {
140
-		self::$loadedApps[] = $app;
141
-		$appPath = self::getAppPath($app);
142
-		if($appPath === false) {
143
-			return;
144
-		}
145
-
146
-		// in case someone calls loadApp() directly
147
-		self::registerAutoloading($app, $appPath);
148
-
149
-		if (is_file($appPath . '/appinfo/app.php')) {
150
-			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
151
-			try {
152
-				self::requireAppFile($app);
153
-			} catch (Error $ex) {
154
-				\OC::$server->getLogger()->logException($ex);
155
-				if (!\OC::$server->getAppManager()->isShipped($app)) {
156
-					// Only disable apps which are not shipped
157
-					\OC::$server->getAppManager()->disableApp($app);
158
-				}
159
-			}
160
-			\OC::$server->getEventLogger()->end('load_app_' . $app);
161
-		}
162
-
163
-		$info = self::getAppInfo($app);
164
-		if (!empty($info['activity']['filters'])) {
165
-			foreach ($info['activity']['filters'] as $filter) {
166
-				\OC::$server->getActivityManager()->registerFilter($filter);
167
-			}
168
-		}
169
-		if (!empty($info['activity']['settings'])) {
170
-			foreach ($info['activity']['settings'] as $setting) {
171
-				\OC::$server->getActivityManager()->registerSetting($setting);
172
-			}
173
-		}
174
-		if (!empty($info['activity']['providers'])) {
175
-			foreach ($info['activity']['providers'] as $provider) {
176
-				\OC::$server->getActivityManager()->registerProvider($provider);
177
-			}
178
-		}
179
-
180
-		if (!empty($info['settings']['admin'])) {
181
-			foreach ($info['settings']['admin'] as $setting) {
182
-				\OC::$server->getSettingsManager()->registerSetting('admin', $setting);
183
-			}
184
-		}
185
-		if (!empty($info['settings']['admin-section'])) {
186
-			foreach ($info['settings']['admin-section'] as $section) {
187
-				\OC::$server->getSettingsManager()->registerSection('admin', $section);
188
-			}
189
-		}
190
-		if (!empty($info['settings']['personal'])) {
191
-			foreach ($info['settings']['personal'] as $setting) {
192
-				\OC::$server->getSettingsManager()->registerSetting('personal', $setting);
193
-			}
194
-		}
195
-		if (!empty($info['settings']['personal-section'])) {
196
-			foreach ($info['settings']['personal-section'] as $section) {
197
-				\OC::$server->getSettingsManager()->registerSection('personal', $section);
198
-			}
199
-		}
200
-
201
-		if (!empty($info['collaboration']['plugins'])) {
202
-			// deal with one or many plugin entries
203
-			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
204
-				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
205
-			foreach ($plugins as $plugin) {
206
-				if($plugin['@attributes']['type'] === 'collaborator-search') {
207
-					$pluginInfo = [
208
-						'shareType' => $plugin['@attributes']['share-type'],
209
-						'class' => $plugin['@value'],
210
-					];
211
-					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
212
-				} else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
213
-					\OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
214
-				}
215
-			}
216
-		}
217
-	}
218
-
219
-	/**
220
-	 * @internal
221
-	 * @param string $app
222
-	 * @param string $path
223
-	 */
224
-	public static function registerAutoloading(string $app, string $path) {
225
-		$key = $app . '-' . $path;
226
-		if(isset(self::$alreadyRegistered[$key])) {
227
-			return;
228
-		}
229
-
230
-		self::$alreadyRegistered[$key] = true;
231
-
232
-		// Register on PSR-4 composer autoloader
233
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
234
-		\OC::$server->registerNamespace($app, $appNamespace);
235
-
236
-		if (file_exists($path . '/composer/autoload.php')) {
237
-			require_once $path . '/composer/autoload.php';
238
-		} else {
239
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
240
-			// Register on legacy autoloader
241
-			\OC::$loader->addValidRoot($path);
242
-		}
243
-
244
-		// Register Test namespace only when testing
245
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
246
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
247
-		}
248
-	}
249
-
250
-	/**
251
-	 * Load app.php from the given app
252
-	 *
253
-	 * @param string $app app name
254
-	 * @throws Error
255
-	 */
256
-	private static function requireAppFile(string $app) {
257
-		// encapsulated here to avoid variable scope conflicts
258
-		require_once $app . '/appinfo/app.php';
259
-	}
260
-
261
-	/**
262
-	 * check if an app is of a specific type
263
-	 *
264
-	 * @param string $app
265
-	 * @param array $types
266
-	 * @return bool
267
-	 */
268
-	public static function isType(string $app, array $types): bool {
269
-		$appTypes = self::getAppTypes($app);
270
-		foreach ($types as $type) {
271
-			if (array_search($type, $appTypes) !== false) {
272
-				return true;
273
-			}
274
-		}
275
-		return false;
276
-	}
277
-
278
-	/**
279
-	 * get the types of an app
280
-	 *
281
-	 * @param string $app
282
-	 * @return array
283
-	 */
284
-	private static function getAppTypes(string $app): array {
285
-		//load the cache
286
-		if (count(self::$appTypes) == 0) {
287
-			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
288
-		}
289
-
290
-		if (isset(self::$appTypes[$app])) {
291
-			return explode(',', self::$appTypes[$app]);
292
-		}
293
-
294
-		return [];
295
-	}
296
-
297
-	/**
298
-	 * read app types from info.xml and cache them in the database
299
-	 */
300
-	public static function setAppTypes(string $app) {
301
-		$appManager = \OC::$server->getAppManager();
302
-		$appData = $appManager->getAppInfo($app);
303
-		if(!is_array($appData)) {
304
-			return;
305
-		}
306
-
307
-		if (isset($appData['types'])) {
308
-			$appTypes = implode(',', $appData['types']);
309
-		} else {
310
-			$appTypes = '';
311
-			$appData['types'] = [];
312
-		}
313
-
314
-		$config = \OC::$server->getConfig();
315
-		$config->setAppValue($app, 'types', $appTypes);
316
-
317
-		if ($appManager->hasProtectedAppType($appData['types'])) {
318
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
319
-			if ($enabled !== 'yes' && $enabled !== 'no') {
320
-				$config->setAppValue($app, 'enabled', 'yes');
321
-			}
322
-		}
323
-	}
324
-
325
-	/**
326
-	 * Returns apps enabled for the current user.
327
-	 *
328
-	 * @param bool $forceRefresh whether to refresh the cache
329
-	 * @param bool $all whether to return apps for all users, not only the
330
-	 * currently logged in one
331
-	 * @return string[]
332
-	 */
333
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
334
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
335
-			return [];
336
-		}
337
-		// in incognito mode or when logged out, $user will be false,
338
-		// which is also the case during an upgrade
339
-		$appManager = \OC::$server->getAppManager();
340
-		if ($all) {
341
-			$user = null;
342
-		} else {
343
-			$user = \OC::$server->getUserSession()->getUser();
344
-		}
345
-
346
-		if (is_null($user)) {
347
-			$apps = $appManager->getInstalledApps();
348
-		} else {
349
-			$apps = $appManager->getEnabledAppsForUser($user);
350
-		}
351
-		$apps = array_filter($apps, function ($app) {
352
-			return $app !== 'files';//we add this manually
353
-		});
354
-		sort($apps);
355
-		array_unshift($apps, 'files');
356
-		return $apps;
357
-	}
358
-
359
-	/**
360
-	 * checks whether or not an app is enabled
361
-	 *
362
-	 * @param string $app app
363
-	 * @return bool
364
-	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
365
-	 *
366
-	 * This function checks whether or not an app is enabled.
367
-	 */
368
-	public static function isEnabled(string $app): bool {
369
-		return \OC::$server->getAppManager()->isEnabledForUser($app);
370
-	}
371
-
372
-	/**
373
-	 * enables an app
374
-	 *
375
-	 * @param string $appId
376
-	 * @param array $groups (optional) when set, only these groups will have access to the app
377
-	 * @throws \Exception
378
-	 * @return void
379
-	 *
380
-	 * This function set an app as enabled in appconfig.
381
-	 */
382
-	public function enable(string $appId,
383
-						   array $groups = []) {
384
-
385
-		// Check if app is already downloaded
386
-		/** @var Installer $installer */
387
-		$installer = \OC::$server->query(Installer::class);
388
-		$isDownloaded = $installer->isDownloaded($appId);
389
-
390
-		if(!$isDownloaded) {
391
-			$installer->downloadApp($appId);
392
-		}
393
-
394
-		$installer->installApp($appId);
395
-
396
-		$appManager = \OC::$server->getAppManager();
397
-		if ($groups !== []) {
398
-			$groupManager = \OC::$server->getGroupManager();
399
-			$groupsList = [];
400
-			foreach ($groups as $group) {
401
-				$groupItem = $groupManager->get($group);
402
-				if ($groupItem instanceof \OCP\IGroup) {
403
-					$groupsList[] = $groupManager->get($group);
404
-				}
405
-			}
406
-			$appManager->enableAppForGroups($appId, $groupsList);
407
-		} else {
408
-			$appManager->enableApp($appId);
409
-		}
410
-	}
411
-
412
-	/**
413
-	 * Get the path where to install apps
414
-	 *
415
-	 * @return string|false
416
-	 */
417
-	public static function getInstallPath() {
418
-		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
419
-			return false;
420
-		}
421
-
422
-		foreach (OC::$APPSROOTS as $dir) {
423
-			if (isset($dir['writable']) && $dir['writable'] === true) {
424
-				return $dir['path'];
425
-			}
426
-		}
427
-
428
-		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
429
-		return null;
430
-	}
431
-
432
-
433
-	/**
434
-	 * search for an app in all app-directories
435
-	 *
436
-	 * @param string $appId
437
-	 * @return false|string
438
-	 */
439
-	public static function findAppInDirectories(string $appId) {
440
-		$sanitizedAppId = self::cleanAppId($appId);
441
-		if($sanitizedAppId !== $appId) {
442
-			return false;
443
-		}
444
-		static $app_dir = [];
445
-
446
-		if (isset($app_dir[$appId])) {
447
-			return $app_dir[$appId];
448
-		}
449
-
450
-		$possibleApps = [];
451
-		foreach (OC::$APPSROOTS as $dir) {
452
-			if (file_exists($dir['path'] . '/' . $appId)) {
453
-				$possibleApps[] = $dir;
454
-			}
455
-		}
456
-
457
-		if (empty($possibleApps)) {
458
-			return false;
459
-		} elseif (count($possibleApps) === 1) {
460
-			$dir = array_shift($possibleApps);
461
-			$app_dir[$appId] = $dir;
462
-			return $dir;
463
-		} else {
464
-			$versionToLoad = [];
465
-			foreach ($possibleApps as $possibleApp) {
466
-				$version = self::getAppVersionByPath($possibleApp['path']);
467
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
468
-					$versionToLoad = array(
469
-						'dir' => $possibleApp,
470
-						'version' => $version,
471
-					);
472
-				}
473
-			}
474
-			$app_dir[$appId] = $versionToLoad['dir'];
475
-			return $versionToLoad['dir'];
476
-			//TODO - write test
477
-		}
478
-	}
479
-
480
-	/**
481
-	 * Get the directory for the given app.
482
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
483
-	 *
484
-	 * @param string $appId
485
-	 * @return string|false
486
-	 */
487
-	public static function getAppPath(string $appId) {
488
-		if ($appId === null || trim($appId) === '') {
489
-			return false;
490
-		}
491
-
492
-		if (($dir = self::findAppInDirectories($appId)) != false) {
493
-			return $dir['path'] . '/' . $appId;
494
-		}
495
-		return false;
496
-	}
497
-
498
-	/**
499
-	 * Get the path for the given app on the access
500
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
501
-	 *
502
-	 * @param string $appId
503
-	 * @return string|false
504
-	 */
505
-	public static function getAppWebPath(string $appId) {
506
-		if (($dir = self::findAppInDirectories($appId)) != false) {
507
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
508
-		}
509
-		return false;
510
-	}
511
-
512
-	/**
513
-	 * get the last version of the app from appinfo/info.xml
514
-	 *
515
-	 * @param string $appId
516
-	 * @param bool $useCache
517
-	 * @return string
518
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
519
-	 */
520
-	public static function getAppVersion(string $appId, bool $useCache = true): string {
521
-		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
522
-	}
523
-
524
-	/**
525
-	 * get app's version based on it's path
526
-	 *
527
-	 * @param string $path
528
-	 * @return string
529
-	 */
530
-	public static function getAppVersionByPath(string $path): string {
531
-		$infoFile = $path . '/appinfo/info.xml';
532
-		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
533
-		return isset($appData['version']) ? $appData['version'] : '';
534
-	}
535
-
536
-
537
-	/**
538
-	 * Read all app metadata from the info.xml file
539
-	 *
540
-	 * @param string $appId id of the app or the path of the info.xml file
541
-	 * @param bool $path
542
-	 * @param string $lang
543
-	 * @return array|null
544
-	 * @note all data is read from info.xml, not just pre-defined fields
545
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
546
-	 */
547
-	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
548
-		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
549
-	}
550
-
551
-	/**
552
-	 * Returns the navigation
553
-	 *
554
-	 * @return array
555
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
556
-	 *
557
-	 * This function returns an array containing all entries added. The
558
-	 * entries are sorted by the key 'order' ascending. Additional to the keys
559
-	 * given for each app the following keys exist:
560
-	 *   - active: boolean, signals if the user is on this navigation entry
561
-	 */
562
-	public static function getNavigation(): array {
563
-		return OC::$server->getNavigationManager()->getAll();
564
-	}
565
-
566
-	/**
567
-	 * Returns the Settings Navigation
568
-	 *
569
-	 * @return string[]
570
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
571
-	 *
572
-	 * This function returns an array containing all settings pages added. The
573
-	 * entries are sorted by the key 'order' ascending.
574
-	 */
575
-	public static function getSettingsNavigation(): array {
576
-		return OC::$server->getNavigationManager()->getAll('settings');
577
-	}
578
-
579
-	/**
580
-	 * get the id of loaded app
581
-	 *
582
-	 * @return string
583
-	 */
584
-	public static function getCurrentApp(): string {
585
-		$request = \OC::$server->getRequest();
586
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
587
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
588
-		if (empty($topFolder)) {
589
-			$path_info = $request->getPathInfo();
590
-			if ($path_info) {
591
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
592
-			}
593
-		}
594
-		if ($topFolder == 'apps') {
595
-			$length = strlen($topFolder);
596
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
597
-		} else {
598
-			return $topFolder;
599
-		}
600
-	}
601
-
602
-	/**
603
-	 * @param string $type
604
-	 * @return array
605
-	 */
606
-	public static function getForms(string $type): array {
607
-		$forms = [];
608
-		switch ($type) {
609
-			case 'admin':
610
-				$source = self::$adminForms;
611
-				break;
612
-			case 'personal':
613
-				$source = self::$personalForms;
614
-				break;
615
-			default:
616
-				return [];
617
-		}
618
-		foreach ($source as $form) {
619
-			$forms[] = include $form;
620
-		}
621
-		return $forms;
622
-	}
623
-
624
-	/**
625
-	 * register an admin form to be shown
626
-	 *
627
-	 * @param string $app
628
-	 * @param string $page
629
-	 */
630
-	public static function registerAdmin(string $app, string $page) {
631
-		self::$adminForms[] = $app . '/' . $page . '.php';
632
-	}
633
-
634
-	/**
635
-	 * register a personal form to be shown
636
-	 * @param string $app
637
-	 * @param string $page
638
-	 */
639
-	public static function registerPersonal(string $app, string $page) {
640
-		self::$personalForms[] = $app . '/' . $page . '.php';
641
-	}
642
-
643
-	/**
644
-	 * @param array $entry
645
-	 */
646
-	public static function registerLogIn(array $entry) {
647
-		self::$altLogin[] = $entry;
648
-	}
649
-
650
-	/**
651
-	 * @return array
652
-	 */
653
-	public static function getAlternativeLogIns(): array {
654
-		return self::$altLogin;
655
-	}
656
-
657
-	/**
658
-	 * get a list of all apps in the apps folder
659
-	 *
660
-	 * @return array an array of app names (string IDs)
661
-	 * @todo: change the name of this method to getInstalledApps, which is more accurate
662
-	 */
663
-	public static function getAllApps(): array {
664
-
665
-		$apps = [];
666
-
667
-		foreach (OC::$APPSROOTS as $apps_dir) {
668
-			if (!is_readable($apps_dir['path'])) {
669
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
670
-				continue;
671
-			}
672
-			$dh = opendir($apps_dir['path']);
673
-
674
-			if (is_resource($dh)) {
675
-				while (($file = readdir($dh)) !== false) {
676
-
677
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
678
-
679
-						$apps[] = $file;
680
-					}
681
-				}
682
-			}
683
-		}
684
-
685
-		$apps = array_unique($apps);
686
-
687
-		return $apps;
688
-	}
689
-
690
-	/**
691
-	 * List all apps, this is used in apps.php
692
-	 *
693
-	 * @return array
694
-	 */
695
-	public function listAllApps(): array {
696
-		$installedApps = OC_App::getAllApps();
697
-
698
-		$appManager = \OC::$server->getAppManager();
699
-		//we don't want to show configuration for these
700
-		$blacklist = $appManager->getAlwaysEnabledApps();
701
-		$appList = [];
702
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
703
-		$urlGenerator = \OC::$server->getURLGenerator();
704
-
705
-		foreach ($installedApps as $app) {
706
-			if (array_search($app, $blacklist) === false) {
707
-
708
-				$info = OC_App::getAppInfo($app, false, $langCode);
709
-				if (!is_array($info)) {
710
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
711
-					continue;
712
-				}
713
-
714
-				if (!isset($info['name'])) {
715
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
716
-					continue;
717
-				}
718
-
719
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
720
-				$info['groups'] = null;
721
-				if ($enabled === 'yes') {
722
-					$active = true;
723
-				} else if ($enabled === 'no') {
724
-					$active = false;
725
-				} else {
726
-					$active = true;
727
-					$info['groups'] = $enabled;
728
-				}
729
-
730
-				$info['active'] = $active;
731
-
732
-				if ($appManager->isShipped($app)) {
733
-					$info['internal'] = true;
734
-					$info['level'] = self::officialApp;
735
-					$info['removable'] = false;
736
-				} else {
737
-					$info['internal'] = false;
738
-					$info['removable'] = true;
739
-				}
740
-
741
-				$appPath = self::getAppPath($app);
742
-				if($appPath !== false) {
743
-					$appIcon = $appPath . '/img/' . $app . '.svg';
744
-					if (file_exists($appIcon)) {
745
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
746
-						$info['previewAsIcon'] = true;
747
-					} else {
748
-						$appIcon = $appPath . '/img/app.svg';
749
-						if (file_exists($appIcon)) {
750
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
751
-							$info['previewAsIcon'] = true;
752
-						}
753
-					}
754
-				}
755
-				// fix documentation
756
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
757
-					foreach ($info['documentation'] as $key => $url) {
758
-						// If it is not an absolute URL we assume it is a key
759
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
760
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
761
-							$url = $urlGenerator->linkToDocs($url);
762
-						}
763
-
764
-						$info['documentation'][$key] = $url;
765
-					}
766
-				}
767
-
768
-				$info['version'] = OC_App::getAppVersion($app);
769
-				$appList[] = $info;
770
-			}
771
-		}
772
-
773
-		return $appList;
774
-	}
775
-
776
-	public static function shouldUpgrade(string $app): bool {
777
-		$versions = self::getAppVersions();
778
-		$currentVersion = OC_App::getAppVersion($app);
779
-		if ($currentVersion && isset($versions[$app])) {
780
-			$installedVersion = $versions[$app];
781
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
782
-				return true;
783
-			}
784
-		}
785
-		return false;
786
-	}
787
-
788
-	/**
789
-	 * Adjust the number of version parts of $version1 to match
790
-	 * the number of version parts of $version2.
791
-	 *
792
-	 * @param string $version1 version to adjust
793
-	 * @param string $version2 version to take the number of parts from
794
-	 * @return string shortened $version1
795
-	 */
796
-	private static function adjustVersionParts(string $version1, string $version2): string {
797
-		$version1 = explode('.', $version1);
798
-		$version2 = explode('.', $version2);
799
-		// reduce $version1 to match the number of parts in $version2
800
-		while (count($version1) > count($version2)) {
801
-			array_pop($version1);
802
-		}
803
-		// if $version1 does not have enough parts, add some
804
-		while (count($version1) < count($version2)) {
805
-			$version1[] = '0';
806
-		}
807
-		return implode('.', $version1);
808
-	}
809
-
810
-	/**
811
-	 * Check whether the current ownCloud version matches the given
812
-	 * application's version requirements.
813
-	 *
814
-	 * The comparison is made based on the number of parts that the
815
-	 * app info version has. For example for ownCloud 6.0.3 if the
816
-	 * app info version is expecting version 6.0, the comparison is
817
-	 * made on the first two parts of the ownCloud version.
818
-	 * This means that it's possible to specify "requiremin" => 6
819
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
820
-	 *
821
-	 * @param string $ocVersion ownCloud version to check against
822
-	 * @param array $appInfo app info (from xml)
823
-	 *
824
-	 * @return boolean true if compatible, otherwise false
825
-	 */
826
-	public static function isAppCompatible(string $ocVersion, array $appInfo): bool {
827
-		$requireMin = '';
828
-		$requireMax = '';
829
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
830
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
831
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
832
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
833
-		} else if (isset($appInfo['requiremin'])) {
834
-			$requireMin = $appInfo['requiremin'];
835
-		} else if (isset($appInfo['require'])) {
836
-			$requireMin = $appInfo['require'];
837
-		}
838
-
839
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
840
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
841
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
842
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
843
-		} else if (isset($appInfo['requiremax'])) {
844
-			$requireMax = $appInfo['requiremax'];
845
-		}
846
-
847
-		if (!empty($requireMin)
848
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
849
-		) {
850
-
851
-			return false;
852
-		}
853
-
854
-		if (!empty($requireMax)
855
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
856
-		) {
857
-			return false;
858
-		}
859
-
860
-		return true;
861
-	}
862
-
863
-	/**
864
-	 * get the installed version of all apps
865
-	 */
866
-	public static function getAppVersions() {
867
-		static $versions;
868
-
869
-		if(!$versions) {
870
-			$appConfig = \OC::$server->getAppConfig();
871
-			$versions = $appConfig->getValues(false, 'installed_version');
872
-		}
873
-		return $versions;
874
-	}
875
-
876
-	/**
877
-	 * update the database for the app and call the update script
878
-	 *
879
-	 * @param string $appId
880
-	 * @return bool
881
-	 */
882
-	public static function updateApp(string $appId): bool {
883
-		$appPath = self::getAppPath($appId);
884
-		if($appPath === false) {
885
-			return false;
886
-		}
887
-		self::registerAutoloading($appId, $appPath);
888
-
889
-		$appData = self::getAppInfo($appId);
890
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
891
-
892
-		if (file_exists($appPath . '/appinfo/database.xml')) {
893
-			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
894
-		} else {
895
-			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
896
-			$ms->migrate();
897
-		}
898
-
899
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
900
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
901
-		// update appversion in app manager
902
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
903
-
904
-		// run upgrade code
905
-		if (file_exists($appPath . '/appinfo/update.php')) {
906
-			self::loadApp($appId);
907
-			include $appPath . '/appinfo/update.php';
908
-		}
909
-		self::setupBackgroundJobs($appData['background-jobs']);
910
-
911
-		//set remote/public handlers
912
-		if (array_key_exists('ocsid', $appData)) {
913
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
914
-		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
915
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
916
-		}
917
-		foreach ($appData['remote'] as $name => $path) {
918
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
919
-		}
920
-		foreach ($appData['public'] as $name => $path) {
921
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
922
-		}
923
-
924
-		self::setAppTypes($appId);
925
-
926
-		$version = \OC_App::getAppVersion($appId);
927
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
928
-
929
-		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
930
-			ManagerEvent::EVENT_APP_UPDATE, $appId
931
-		));
932
-
933
-		return true;
934
-	}
935
-
936
-	/**
937
-	 * @param string $appId
938
-	 * @param string[] $steps
939
-	 * @throws \OC\NeedsUpdateException
940
-	 */
941
-	public static function executeRepairSteps(string $appId, array $steps) {
942
-		if (empty($steps)) {
943
-			return;
944
-		}
945
-		// load the app
946
-		self::loadApp($appId);
947
-
948
-		$dispatcher = OC::$server->getEventDispatcher();
949
-
950
-		// load the steps
951
-		$r = new Repair([], $dispatcher);
952
-		foreach ($steps as $step) {
953
-			try {
954
-				$r->addStep($step);
955
-			} catch (Exception $ex) {
956
-				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
957
-				\OC::$server->getLogger()->logException($ex);
958
-			}
959
-		}
960
-		// run the steps
961
-		$r->run();
962
-	}
963
-
964
-	public static function setupBackgroundJobs(array $jobs) {
965
-		$queue = \OC::$server->getJobList();
966
-		foreach ($jobs as $job) {
967
-			$queue->add($job);
968
-		}
969
-	}
970
-
971
-	/**
972
-	 * @param string $appId
973
-	 * @param string[] $steps
974
-	 */
975
-	private static function setupLiveMigrations(string $appId, array $steps) {
976
-		$queue = \OC::$server->getJobList();
977
-		foreach ($steps as $step) {
978
-			$queue->add('OC\Migration\BackgroundRepair', [
979
-				'app' => $appId,
980
-				'step' => $step]);
981
-		}
982
-	}
983
-
984
-	/**
985
-	 * @param string $appId
986
-	 * @return \OC\Files\View|false
987
-	 */
988
-	public static function getStorage(string $appId) {
989
-		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
990
-			if (\OC::$server->getUserSession()->isLoggedIn()) {
991
-				$view = new \OC\Files\View('/' . OC_User::getUser());
992
-				if (!$view->file_exists($appId)) {
993
-					$view->mkdir($appId);
994
-				}
995
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
996
-			} else {
997
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
998
-				return false;
999
-			}
1000
-		} else {
1001
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1002
-			return false;
1003
-		}
1004
-	}
1005
-
1006
-	protected static function findBestL10NOption(array $options, string $lang): string {
1007
-		$fallback = $similarLangFallback = $englishFallback = false;
1008
-
1009
-		$lang = strtolower($lang);
1010
-		$similarLang = $lang;
1011
-		if (strpos($similarLang, '_')) {
1012
-			// For "de_DE" we want to find "de" and the other way around
1013
-			$similarLang = substr($lang, 0, strpos($lang, '_'));
1014
-		}
1015
-
1016
-		foreach ($options as $option) {
1017
-			if (is_array($option)) {
1018
-				if ($fallback === false) {
1019
-					$fallback = $option['@value'];
1020
-				}
1021
-
1022
-				if (!isset($option['@attributes']['lang'])) {
1023
-					continue;
1024
-				}
1025
-
1026
-				$attributeLang = strtolower($option['@attributes']['lang']);
1027
-				if ($attributeLang === $lang) {
1028
-					return $option['@value'];
1029
-				}
1030
-
1031
-				if ($attributeLang === $similarLang) {
1032
-					$similarLangFallback = $option['@value'];
1033
-				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1034
-					if ($similarLangFallback === false) {
1035
-						$similarLangFallback =  $option['@value'];
1036
-					}
1037
-				}
1038
-			} else {
1039
-				$englishFallback = $option;
1040
-			}
1041
-		}
1042
-
1043
-		if ($similarLangFallback !== false) {
1044
-			return $similarLangFallback;
1045
-		} else if ($englishFallback !== false) {
1046
-			return $englishFallback;
1047
-		}
1048
-		return (string) $fallback;
1049
-	}
1050
-
1051
-	/**
1052
-	 * parses the app data array and enhanced the 'description' value
1053
-	 *
1054
-	 * @param array $data the app data
1055
-	 * @param string $lang
1056
-	 * @return array improved app data
1057
-	 */
1058
-	public static function parseAppInfo(array $data, $lang = null): array {
1059
-
1060
-		if ($lang && isset($data['name']) && is_array($data['name'])) {
1061
-			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1062
-		}
1063
-		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1064
-			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1065
-		}
1066
-		if ($lang && isset($data['description']) && is_array($data['description'])) {
1067
-			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1068
-		} else if (isset($data['description']) && is_string($data['description'])) {
1069
-			$data['description'] = trim($data['description']);
1070
-		} else  {
1071
-			$data['description'] = '';
1072
-		}
1073
-
1074
-		return $data;
1075
-	}
1076
-
1077
-	/**
1078
-	 * @param \OCP\IConfig $config
1079
-	 * @param \OCP\IL10N $l
1080
-	 * @param array $info
1081
-	 * @throws \Exception
1082
-	 */
1083
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info) {
1084
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1085
-		$missing = $dependencyAnalyzer->analyze($info);
1086
-		if (!empty($missing)) {
1087
-			$missingMsg = implode(PHP_EOL, $missing);
1088
-			throw new \Exception(
1089
-				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1090
-					[$info['name'], $missingMsg]
1091
-				)
1092
-			);
1093
-		}
1094
-	}
66
+    static private $adminForms = [];
67
+    static private $personalForms = [];
68
+    static private $appTypes = [];
69
+    static private $loadedApps = [];
70
+    static private $altLogin = [];
71
+    static private $alreadyRegistered = [];
72
+    const officialApp = 200;
73
+
74
+    /**
75
+     * clean the appId
76
+     *
77
+     * @param string $app AppId that needs to be cleaned
78
+     * @return string
79
+     */
80
+    public static function cleanAppId(string $app): string {
81
+        return str_replace(array('\0', '/', '\\', '..'), '', $app);
82
+    }
83
+
84
+    /**
85
+     * Check if an app is loaded
86
+     *
87
+     * @param string $app
88
+     * @return bool
89
+     */
90
+    public static function isAppLoaded(string $app): bool {
91
+        return in_array($app, self::$loadedApps, true);
92
+    }
93
+
94
+    /**
95
+     * loads all apps
96
+     *
97
+     * @param string[] $types
98
+     * @return bool
99
+     *
100
+     * This function walks through the ownCloud directory and loads all apps
101
+     * it can find. A directory contains an app if the file /appinfo/info.xml
102
+     * exists.
103
+     *
104
+     * if $types is set to non-empty array, only apps of those types will be loaded
105
+     */
106
+    public static function loadApps(array $types = []): bool {
107
+        if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
108
+            return false;
109
+        }
110
+        // Load the enabled apps here
111
+        $apps = self::getEnabledApps();
112
+
113
+        // Add each apps' folder as allowed class path
114
+        foreach($apps as $app) {
115
+            $path = self::getAppPath($app);
116
+            if($path !== false) {
117
+                self::registerAutoloading($app, $path);
118
+            }
119
+        }
120
+
121
+        // prevent app.php from printing output
122
+        ob_start();
123
+        foreach ($apps as $app) {
124
+            if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
125
+                self::loadApp($app);
126
+            }
127
+        }
128
+        ob_end_clean();
129
+
130
+        return true;
131
+    }
132
+
133
+    /**
134
+     * load a single app
135
+     *
136
+     * @param string $app
137
+     * @throws Exception
138
+     */
139
+    public static function loadApp(string $app) {
140
+        self::$loadedApps[] = $app;
141
+        $appPath = self::getAppPath($app);
142
+        if($appPath === false) {
143
+            return;
144
+        }
145
+
146
+        // in case someone calls loadApp() directly
147
+        self::registerAutoloading($app, $appPath);
148
+
149
+        if (is_file($appPath . '/appinfo/app.php')) {
150
+            \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
151
+            try {
152
+                self::requireAppFile($app);
153
+            } catch (Error $ex) {
154
+                \OC::$server->getLogger()->logException($ex);
155
+                if (!\OC::$server->getAppManager()->isShipped($app)) {
156
+                    // Only disable apps which are not shipped
157
+                    \OC::$server->getAppManager()->disableApp($app);
158
+                }
159
+            }
160
+            \OC::$server->getEventLogger()->end('load_app_' . $app);
161
+        }
162
+
163
+        $info = self::getAppInfo($app);
164
+        if (!empty($info['activity']['filters'])) {
165
+            foreach ($info['activity']['filters'] as $filter) {
166
+                \OC::$server->getActivityManager()->registerFilter($filter);
167
+            }
168
+        }
169
+        if (!empty($info['activity']['settings'])) {
170
+            foreach ($info['activity']['settings'] as $setting) {
171
+                \OC::$server->getActivityManager()->registerSetting($setting);
172
+            }
173
+        }
174
+        if (!empty($info['activity']['providers'])) {
175
+            foreach ($info['activity']['providers'] as $provider) {
176
+                \OC::$server->getActivityManager()->registerProvider($provider);
177
+            }
178
+        }
179
+
180
+        if (!empty($info['settings']['admin'])) {
181
+            foreach ($info['settings']['admin'] as $setting) {
182
+                \OC::$server->getSettingsManager()->registerSetting('admin', $setting);
183
+            }
184
+        }
185
+        if (!empty($info['settings']['admin-section'])) {
186
+            foreach ($info['settings']['admin-section'] as $section) {
187
+                \OC::$server->getSettingsManager()->registerSection('admin', $section);
188
+            }
189
+        }
190
+        if (!empty($info['settings']['personal'])) {
191
+            foreach ($info['settings']['personal'] as $setting) {
192
+                \OC::$server->getSettingsManager()->registerSetting('personal', $setting);
193
+            }
194
+        }
195
+        if (!empty($info['settings']['personal-section'])) {
196
+            foreach ($info['settings']['personal-section'] as $section) {
197
+                \OC::$server->getSettingsManager()->registerSection('personal', $section);
198
+            }
199
+        }
200
+
201
+        if (!empty($info['collaboration']['plugins'])) {
202
+            // deal with one or many plugin entries
203
+            $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
204
+                [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
205
+            foreach ($plugins as $plugin) {
206
+                if($plugin['@attributes']['type'] === 'collaborator-search') {
207
+                    $pluginInfo = [
208
+                        'shareType' => $plugin['@attributes']['share-type'],
209
+                        'class' => $plugin['@value'],
210
+                    ];
211
+                    \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
212
+                } else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
213
+                    \OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
214
+                }
215
+            }
216
+        }
217
+    }
218
+
219
+    /**
220
+     * @internal
221
+     * @param string $app
222
+     * @param string $path
223
+     */
224
+    public static function registerAutoloading(string $app, string $path) {
225
+        $key = $app . '-' . $path;
226
+        if(isset(self::$alreadyRegistered[$key])) {
227
+            return;
228
+        }
229
+
230
+        self::$alreadyRegistered[$key] = true;
231
+
232
+        // Register on PSR-4 composer autoloader
233
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
234
+        \OC::$server->registerNamespace($app, $appNamespace);
235
+
236
+        if (file_exists($path . '/composer/autoload.php')) {
237
+            require_once $path . '/composer/autoload.php';
238
+        } else {
239
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
240
+            // Register on legacy autoloader
241
+            \OC::$loader->addValidRoot($path);
242
+        }
243
+
244
+        // Register Test namespace only when testing
245
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
246
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
247
+        }
248
+    }
249
+
250
+    /**
251
+     * Load app.php from the given app
252
+     *
253
+     * @param string $app app name
254
+     * @throws Error
255
+     */
256
+    private static function requireAppFile(string $app) {
257
+        // encapsulated here to avoid variable scope conflicts
258
+        require_once $app . '/appinfo/app.php';
259
+    }
260
+
261
+    /**
262
+     * check if an app is of a specific type
263
+     *
264
+     * @param string $app
265
+     * @param array $types
266
+     * @return bool
267
+     */
268
+    public static function isType(string $app, array $types): bool {
269
+        $appTypes = self::getAppTypes($app);
270
+        foreach ($types as $type) {
271
+            if (array_search($type, $appTypes) !== false) {
272
+                return true;
273
+            }
274
+        }
275
+        return false;
276
+    }
277
+
278
+    /**
279
+     * get the types of an app
280
+     *
281
+     * @param string $app
282
+     * @return array
283
+     */
284
+    private static function getAppTypes(string $app): array {
285
+        //load the cache
286
+        if (count(self::$appTypes) == 0) {
287
+            self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
288
+        }
289
+
290
+        if (isset(self::$appTypes[$app])) {
291
+            return explode(',', self::$appTypes[$app]);
292
+        }
293
+
294
+        return [];
295
+    }
296
+
297
+    /**
298
+     * read app types from info.xml and cache them in the database
299
+     */
300
+    public static function setAppTypes(string $app) {
301
+        $appManager = \OC::$server->getAppManager();
302
+        $appData = $appManager->getAppInfo($app);
303
+        if(!is_array($appData)) {
304
+            return;
305
+        }
306
+
307
+        if (isset($appData['types'])) {
308
+            $appTypes = implode(',', $appData['types']);
309
+        } else {
310
+            $appTypes = '';
311
+            $appData['types'] = [];
312
+        }
313
+
314
+        $config = \OC::$server->getConfig();
315
+        $config->setAppValue($app, 'types', $appTypes);
316
+
317
+        if ($appManager->hasProtectedAppType($appData['types'])) {
318
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
319
+            if ($enabled !== 'yes' && $enabled !== 'no') {
320
+                $config->setAppValue($app, 'enabled', 'yes');
321
+            }
322
+        }
323
+    }
324
+
325
+    /**
326
+     * Returns apps enabled for the current user.
327
+     *
328
+     * @param bool $forceRefresh whether to refresh the cache
329
+     * @param bool $all whether to return apps for all users, not only the
330
+     * currently logged in one
331
+     * @return string[]
332
+     */
333
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
334
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
335
+            return [];
336
+        }
337
+        // in incognito mode or when logged out, $user will be false,
338
+        // which is also the case during an upgrade
339
+        $appManager = \OC::$server->getAppManager();
340
+        if ($all) {
341
+            $user = null;
342
+        } else {
343
+            $user = \OC::$server->getUserSession()->getUser();
344
+        }
345
+
346
+        if (is_null($user)) {
347
+            $apps = $appManager->getInstalledApps();
348
+        } else {
349
+            $apps = $appManager->getEnabledAppsForUser($user);
350
+        }
351
+        $apps = array_filter($apps, function ($app) {
352
+            return $app !== 'files';//we add this manually
353
+        });
354
+        sort($apps);
355
+        array_unshift($apps, 'files');
356
+        return $apps;
357
+    }
358
+
359
+    /**
360
+     * checks whether or not an app is enabled
361
+     *
362
+     * @param string $app app
363
+     * @return bool
364
+     * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
365
+     *
366
+     * This function checks whether or not an app is enabled.
367
+     */
368
+    public static function isEnabled(string $app): bool {
369
+        return \OC::$server->getAppManager()->isEnabledForUser($app);
370
+    }
371
+
372
+    /**
373
+     * enables an app
374
+     *
375
+     * @param string $appId
376
+     * @param array $groups (optional) when set, only these groups will have access to the app
377
+     * @throws \Exception
378
+     * @return void
379
+     *
380
+     * This function set an app as enabled in appconfig.
381
+     */
382
+    public function enable(string $appId,
383
+                            array $groups = []) {
384
+
385
+        // Check if app is already downloaded
386
+        /** @var Installer $installer */
387
+        $installer = \OC::$server->query(Installer::class);
388
+        $isDownloaded = $installer->isDownloaded($appId);
389
+
390
+        if(!$isDownloaded) {
391
+            $installer->downloadApp($appId);
392
+        }
393
+
394
+        $installer->installApp($appId);
395
+
396
+        $appManager = \OC::$server->getAppManager();
397
+        if ($groups !== []) {
398
+            $groupManager = \OC::$server->getGroupManager();
399
+            $groupsList = [];
400
+            foreach ($groups as $group) {
401
+                $groupItem = $groupManager->get($group);
402
+                if ($groupItem instanceof \OCP\IGroup) {
403
+                    $groupsList[] = $groupManager->get($group);
404
+                }
405
+            }
406
+            $appManager->enableAppForGroups($appId, $groupsList);
407
+        } else {
408
+            $appManager->enableApp($appId);
409
+        }
410
+    }
411
+
412
+    /**
413
+     * Get the path where to install apps
414
+     *
415
+     * @return string|false
416
+     */
417
+    public static function getInstallPath() {
418
+        if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
419
+            return false;
420
+        }
421
+
422
+        foreach (OC::$APPSROOTS as $dir) {
423
+            if (isset($dir['writable']) && $dir['writable'] === true) {
424
+                return $dir['path'];
425
+            }
426
+        }
427
+
428
+        \OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
429
+        return null;
430
+    }
431
+
432
+
433
+    /**
434
+     * search for an app in all app-directories
435
+     *
436
+     * @param string $appId
437
+     * @return false|string
438
+     */
439
+    public static function findAppInDirectories(string $appId) {
440
+        $sanitizedAppId = self::cleanAppId($appId);
441
+        if($sanitizedAppId !== $appId) {
442
+            return false;
443
+        }
444
+        static $app_dir = [];
445
+
446
+        if (isset($app_dir[$appId])) {
447
+            return $app_dir[$appId];
448
+        }
449
+
450
+        $possibleApps = [];
451
+        foreach (OC::$APPSROOTS as $dir) {
452
+            if (file_exists($dir['path'] . '/' . $appId)) {
453
+                $possibleApps[] = $dir;
454
+            }
455
+        }
456
+
457
+        if (empty($possibleApps)) {
458
+            return false;
459
+        } elseif (count($possibleApps) === 1) {
460
+            $dir = array_shift($possibleApps);
461
+            $app_dir[$appId] = $dir;
462
+            return $dir;
463
+        } else {
464
+            $versionToLoad = [];
465
+            foreach ($possibleApps as $possibleApp) {
466
+                $version = self::getAppVersionByPath($possibleApp['path']);
467
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
468
+                    $versionToLoad = array(
469
+                        'dir' => $possibleApp,
470
+                        'version' => $version,
471
+                    );
472
+                }
473
+            }
474
+            $app_dir[$appId] = $versionToLoad['dir'];
475
+            return $versionToLoad['dir'];
476
+            //TODO - write test
477
+        }
478
+    }
479
+
480
+    /**
481
+     * Get the directory for the given app.
482
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
483
+     *
484
+     * @param string $appId
485
+     * @return string|false
486
+     */
487
+    public static function getAppPath(string $appId) {
488
+        if ($appId === null || trim($appId) === '') {
489
+            return false;
490
+        }
491
+
492
+        if (($dir = self::findAppInDirectories($appId)) != false) {
493
+            return $dir['path'] . '/' . $appId;
494
+        }
495
+        return false;
496
+    }
497
+
498
+    /**
499
+     * Get the path for the given app on the access
500
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
501
+     *
502
+     * @param string $appId
503
+     * @return string|false
504
+     */
505
+    public static function getAppWebPath(string $appId) {
506
+        if (($dir = self::findAppInDirectories($appId)) != false) {
507
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
508
+        }
509
+        return false;
510
+    }
511
+
512
+    /**
513
+     * get the last version of the app from appinfo/info.xml
514
+     *
515
+     * @param string $appId
516
+     * @param bool $useCache
517
+     * @return string
518
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
519
+     */
520
+    public static function getAppVersion(string $appId, bool $useCache = true): string {
521
+        return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
522
+    }
523
+
524
+    /**
525
+     * get app's version based on it's path
526
+     *
527
+     * @param string $path
528
+     * @return string
529
+     */
530
+    public static function getAppVersionByPath(string $path): string {
531
+        $infoFile = $path . '/appinfo/info.xml';
532
+        $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
533
+        return isset($appData['version']) ? $appData['version'] : '';
534
+    }
535
+
536
+
537
+    /**
538
+     * Read all app metadata from the info.xml file
539
+     *
540
+     * @param string $appId id of the app or the path of the info.xml file
541
+     * @param bool $path
542
+     * @param string $lang
543
+     * @return array|null
544
+     * @note all data is read from info.xml, not just pre-defined fields
545
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
546
+     */
547
+    public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
548
+        return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
549
+    }
550
+
551
+    /**
552
+     * Returns the navigation
553
+     *
554
+     * @return array
555
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
556
+     *
557
+     * This function returns an array containing all entries added. The
558
+     * entries are sorted by the key 'order' ascending. Additional to the keys
559
+     * given for each app the following keys exist:
560
+     *   - active: boolean, signals if the user is on this navigation entry
561
+     */
562
+    public static function getNavigation(): array {
563
+        return OC::$server->getNavigationManager()->getAll();
564
+    }
565
+
566
+    /**
567
+     * Returns the Settings Navigation
568
+     *
569
+     * @return string[]
570
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
571
+     *
572
+     * This function returns an array containing all settings pages added. The
573
+     * entries are sorted by the key 'order' ascending.
574
+     */
575
+    public static function getSettingsNavigation(): array {
576
+        return OC::$server->getNavigationManager()->getAll('settings');
577
+    }
578
+
579
+    /**
580
+     * get the id of loaded app
581
+     *
582
+     * @return string
583
+     */
584
+    public static function getCurrentApp(): string {
585
+        $request = \OC::$server->getRequest();
586
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
587
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
588
+        if (empty($topFolder)) {
589
+            $path_info = $request->getPathInfo();
590
+            if ($path_info) {
591
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
592
+            }
593
+        }
594
+        if ($topFolder == 'apps') {
595
+            $length = strlen($topFolder);
596
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
597
+        } else {
598
+            return $topFolder;
599
+        }
600
+    }
601
+
602
+    /**
603
+     * @param string $type
604
+     * @return array
605
+     */
606
+    public static function getForms(string $type): array {
607
+        $forms = [];
608
+        switch ($type) {
609
+            case 'admin':
610
+                $source = self::$adminForms;
611
+                break;
612
+            case 'personal':
613
+                $source = self::$personalForms;
614
+                break;
615
+            default:
616
+                return [];
617
+        }
618
+        foreach ($source as $form) {
619
+            $forms[] = include $form;
620
+        }
621
+        return $forms;
622
+    }
623
+
624
+    /**
625
+     * register an admin form to be shown
626
+     *
627
+     * @param string $app
628
+     * @param string $page
629
+     */
630
+    public static function registerAdmin(string $app, string $page) {
631
+        self::$adminForms[] = $app . '/' . $page . '.php';
632
+    }
633
+
634
+    /**
635
+     * register a personal form to be shown
636
+     * @param string $app
637
+     * @param string $page
638
+     */
639
+    public static function registerPersonal(string $app, string $page) {
640
+        self::$personalForms[] = $app . '/' . $page . '.php';
641
+    }
642
+
643
+    /**
644
+     * @param array $entry
645
+     */
646
+    public static function registerLogIn(array $entry) {
647
+        self::$altLogin[] = $entry;
648
+    }
649
+
650
+    /**
651
+     * @return array
652
+     */
653
+    public static function getAlternativeLogIns(): array {
654
+        return self::$altLogin;
655
+    }
656
+
657
+    /**
658
+     * get a list of all apps in the apps folder
659
+     *
660
+     * @return array an array of app names (string IDs)
661
+     * @todo: change the name of this method to getInstalledApps, which is more accurate
662
+     */
663
+    public static function getAllApps(): array {
664
+
665
+        $apps = [];
666
+
667
+        foreach (OC::$APPSROOTS as $apps_dir) {
668
+            if (!is_readable($apps_dir['path'])) {
669
+                \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
670
+                continue;
671
+            }
672
+            $dh = opendir($apps_dir['path']);
673
+
674
+            if (is_resource($dh)) {
675
+                while (($file = readdir($dh)) !== false) {
676
+
677
+                    if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
678
+
679
+                        $apps[] = $file;
680
+                    }
681
+                }
682
+            }
683
+        }
684
+
685
+        $apps = array_unique($apps);
686
+
687
+        return $apps;
688
+    }
689
+
690
+    /**
691
+     * List all apps, this is used in apps.php
692
+     *
693
+     * @return array
694
+     */
695
+    public function listAllApps(): array {
696
+        $installedApps = OC_App::getAllApps();
697
+
698
+        $appManager = \OC::$server->getAppManager();
699
+        //we don't want to show configuration for these
700
+        $blacklist = $appManager->getAlwaysEnabledApps();
701
+        $appList = [];
702
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
703
+        $urlGenerator = \OC::$server->getURLGenerator();
704
+
705
+        foreach ($installedApps as $app) {
706
+            if (array_search($app, $blacklist) === false) {
707
+
708
+                $info = OC_App::getAppInfo($app, false, $langCode);
709
+                if (!is_array($info)) {
710
+                    \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
711
+                    continue;
712
+                }
713
+
714
+                if (!isset($info['name'])) {
715
+                    \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
716
+                    continue;
717
+                }
718
+
719
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
720
+                $info['groups'] = null;
721
+                if ($enabled === 'yes') {
722
+                    $active = true;
723
+                } else if ($enabled === 'no') {
724
+                    $active = false;
725
+                } else {
726
+                    $active = true;
727
+                    $info['groups'] = $enabled;
728
+                }
729
+
730
+                $info['active'] = $active;
731
+
732
+                if ($appManager->isShipped($app)) {
733
+                    $info['internal'] = true;
734
+                    $info['level'] = self::officialApp;
735
+                    $info['removable'] = false;
736
+                } else {
737
+                    $info['internal'] = false;
738
+                    $info['removable'] = true;
739
+                }
740
+
741
+                $appPath = self::getAppPath($app);
742
+                if($appPath !== false) {
743
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
744
+                    if (file_exists($appIcon)) {
745
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
746
+                        $info['previewAsIcon'] = true;
747
+                    } else {
748
+                        $appIcon = $appPath . '/img/app.svg';
749
+                        if (file_exists($appIcon)) {
750
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
751
+                            $info['previewAsIcon'] = true;
752
+                        }
753
+                    }
754
+                }
755
+                // fix documentation
756
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
757
+                    foreach ($info['documentation'] as $key => $url) {
758
+                        // If it is not an absolute URL we assume it is a key
759
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
760
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
761
+                            $url = $urlGenerator->linkToDocs($url);
762
+                        }
763
+
764
+                        $info['documentation'][$key] = $url;
765
+                    }
766
+                }
767
+
768
+                $info['version'] = OC_App::getAppVersion($app);
769
+                $appList[] = $info;
770
+            }
771
+        }
772
+
773
+        return $appList;
774
+    }
775
+
776
+    public static function shouldUpgrade(string $app): bool {
777
+        $versions = self::getAppVersions();
778
+        $currentVersion = OC_App::getAppVersion($app);
779
+        if ($currentVersion && isset($versions[$app])) {
780
+            $installedVersion = $versions[$app];
781
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
782
+                return true;
783
+            }
784
+        }
785
+        return false;
786
+    }
787
+
788
+    /**
789
+     * Adjust the number of version parts of $version1 to match
790
+     * the number of version parts of $version2.
791
+     *
792
+     * @param string $version1 version to adjust
793
+     * @param string $version2 version to take the number of parts from
794
+     * @return string shortened $version1
795
+     */
796
+    private static function adjustVersionParts(string $version1, string $version2): string {
797
+        $version1 = explode('.', $version1);
798
+        $version2 = explode('.', $version2);
799
+        // reduce $version1 to match the number of parts in $version2
800
+        while (count($version1) > count($version2)) {
801
+            array_pop($version1);
802
+        }
803
+        // if $version1 does not have enough parts, add some
804
+        while (count($version1) < count($version2)) {
805
+            $version1[] = '0';
806
+        }
807
+        return implode('.', $version1);
808
+    }
809
+
810
+    /**
811
+     * Check whether the current ownCloud version matches the given
812
+     * application's version requirements.
813
+     *
814
+     * The comparison is made based on the number of parts that the
815
+     * app info version has. For example for ownCloud 6.0.3 if the
816
+     * app info version is expecting version 6.0, the comparison is
817
+     * made on the first two parts of the ownCloud version.
818
+     * This means that it's possible to specify "requiremin" => 6
819
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
820
+     *
821
+     * @param string $ocVersion ownCloud version to check against
822
+     * @param array $appInfo app info (from xml)
823
+     *
824
+     * @return boolean true if compatible, otherwise false
825
+     */
826
+    public static function isAppCompatible(string $ocVersion, array $appInfo): bool {
827
+        $requireMin = '';
828
+        $requireMax = '';
829
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
830
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
831
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
832
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
833
+        } else if (isset($appInfo['requiremin'])) {
834
+            $requireMin = $appInfo['requiremin'];
835
+        } else if (isset($appInfo['require'])) {
836
+            $requireMin = $appInfo['require'];
837
+        }
838
+
839
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
840
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
841
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
842
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
843
+        } else if (isset($appInfo['requiremax'])) {
844
+            $requireMax = $appInfo['requiremax'];
845
+        }
846
+
847
+        if (!empty($requireMin)
848
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
849
+        ) {
850
+
851
+            return false;
852
+        }
853
+
854
+        if (!empty($requireMax)
855
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
856
+        ) {
857
+            return false;
858
+        }
859
+
860
+        return true;
861
+    }
862
+
863
+    /**
864
+     * get the installed version of all apps
865
+     */
866
+    public static function getAppVersions() {
867
+        static $versions;
868
+
869
+        if(!$versions) {
870
+            $appConfig = \OC::$server->getAppConfig();
871
+            $versions = $appConfig->getValues(false, 'installed_version');
872
+        }
873
+        return $versions;
874
+    }
875
+
876
+    /**
877
+     * update the database for the app and call the update script
878
+     *
879
+     * @param string $appId
880
+     * @return bool
881
+     */
882
+    public static function updateApp(string $appId): bool {
883
+        $appPath = self::getAppPath($appId);
884
+        if($appPath === false) {
885
+            return false;
886
+        }
887
+        self::registerAutoloading($appId, $appPath);
888
+
889
+        $appData = self::getAppInfo($appId);
890
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
891
+
892
+        if (file_exists($appPath . '/appinfo/database.xml')) {
893
+            OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
894
+        } else {
895
+            $ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
896
+            $ms->migrate();
897
+        }
898
+
899
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
900
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
901
+        // update appversion in app manager
902
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
903
+
904
+        // run upgrade code
905
+        if (file_exists($appPath . '/appinfo/update.php')) {
906
+            self::loadApp($appId);
907
+            include $appPath . '/appinfo/update.php';
908
+        }
909
+        self::setupBackgroundJobs($appData['background-jobs']);
910
+
911
+        //set remote/public handlers
912
+        if (array_key_exists('ocsid', $appData)) {
913
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
914
+        } elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
915
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
916
+        }
917
+        foreach ($appData['remote'] as $name => $path) {
918
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
919
+        }
920
+        foreach ($appData['public'] as $name => $path) {
921
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
922
+        }
923
+
924
+        self::setAppTypes($appId);
925
+
926
+        $version = \OC_App::getAppVersion($appId);
927
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
928
+
929
+        \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
930
+            ManagerEvent::EVENT_APP_UPDATE, $appId
931
+        ));
932
+
933
+        return true;
934
+    }
935
+
936
+    /**
937
+     * @param string $appId
938
+     * @param string[] $steps
939
+     * @throws \OC\NeedsUpdateException
940
+     */
941
+    public static function executeRepairSteps(string $appId, array $steps) {
942
+        if (empty($steps)) {
943
+            return;
944
+        }
945
+        // load the app
946
+        self::loadApp($appId);
947
+
948
+        $dispatcher = OC::$server->getEventDispatcher();
949
+
950
+        // load the steps
951
+        $r = new Repair([], $dispatcher);
952
+        foreach ($steps as $step) {
953
+            try {
954
+                $r->addStep($step);
955
+            } catch (Exception $ex) {
956
+                $r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
957
+                \OC::$server->getLogger()->logException($ex);
958
+            }
959
+        }
960
+        // run the steps
961
+        $r->run();
962
+    }
963
+
964
+    public static function setupBackgroundJobs(array $jobs) {
965
+        $queue = \OC::$server->getJobList();
966
+        foreach ($jobs as $job) {
967
+            $queue->add($job);
968
+        }
969
+    }
970
+
971
+    /**
972
+     * @param string $appId
973
+     * @param string[] $steps
974
+     */
975
+    private static function setupLiveMigrations(string $appId, array $steps) {
976
+        $queue = \OC::$server->getJobList();
977
+        foreach ($steps as $step) {
978
+            $queue->add('OC\Migration\BackgroundRepair', [
979
+                'app' => $appId,
980
+                'step' => $step]);
981
+        }
982
+    }
983
+
984
+    /**
985
+     * @param string $appId
986
+     * @return \OC\Files\View|false
987
+     */
988
+    public static function getStorage(string $appId) {
989
+        if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
990
+            if (\OC::$server->getUserSession()->isLoggedIn()) {
991
+                $view = new \OC\Files\View('/' . OC_User::getUser());
992
+                if (!$view->file_exists($appId)) {
993
+                    $view->mkdir($appId);
994
+                }
995
+                return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
996
+            } else {
997
+                \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
998
+                return false;
999
+            }
1000
+        } else {
1001
+            \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1002
+            return false;
1003
+        }
1004
+    }
1005
+
1006
+    protected static function findBestL10NOption(array $options, string $lang): string {
1007
+        $fallback = $similarLangFallback = $englishFallback = false;
1008
+
1009
+        $lang = strtolower($lang);
1010
+        $similarLang = $lang;
1011
+        if (strpos($similarLang, '_')) {
1012
+            // For "de_DE" we want to find "de" and the other way around
1013
+            $similarLang = substr($lang, 0, strpos($lang, '_'));
1014
+        }
1015
+
1016
+        foreach ($options as $option) {
1017
+            if (is_array($option)) {
1018
+                if ($fallback === false) {
1019
+                    $fallback = $option['@value'];
1020
+                }
1021
+
1022
+                if (!isset($option['@attributes']['lang'])) {
1023
+                    continue;
1024
+                }
1025
+
1026
+                $attributeLang = strtolower($option['@attributes']['lang']);
1027
+                if ($attributeLang === $lang) {
1028
+                    return $option['@value'];
1029
+                }
1030
+
1031
+                if ($attributeLang === $similarLang) {
1032
+                    $similarLangFallback = $option['@value'];
1033
+                } else if (strpos($attributeLang, $similarLang . '_') === 0) {
1034
+                    if ($similarLangFallback === false) {
1035
+                        $similarLangFallback =  $option['@value'];
1036
+                    }
1037
+                }
1038
+            } else {
1039
+                $englishFallback = $option;
1040
+            }
1041
+        }
1042
+
1043
+        if ($similarLangFallback !== false) {
1044
+            return $similarLangFallback;
1045
+        } else if ($englishFallback !== false) {
1046
+            return $englishFallback;
1047
+        }
1048
+        return (string) $fallback;
1049
+    }
1050
+
1051
+    /**
1052
+     * parses the app data array and enhanced the 'description' value
1053
+     *
1054
+     * @param array $data the app data
1055
+     * @param string $lang
1056
+     * @return array improved app data
1057
+     */
1058
+    public static function parseAppInfo(array $data, $lang = null): array {
1059
+
1060
+        if ($lang && isset($data['name']) && is_array($data['name'])) {
1061
+            $data['name'] = self::findBestL10NOption($data['name'], $lang);
1062
+        }
1063
+        if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1064
+            $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1065
+        }
1066
+        if ($lang && isset($data['description']) && is_array($data['description'])) {
1067
+            $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1068
+        } else if (isset($data['description']) && is_string($data['description'])) {
1069
+            $data['description'] = trim($data['description']);
1070
+        } else  {
1071
+            $data['description'] = '';
1072
+        }
1073
+
1074
+        return $data;
1075
+    }
1076
+
1077
+    /**
1078
+     * @param \OCP\IConfig $config
1079
+     * @param \OCP\IL10N $l
1080
+     * @param array $info
1081
+     * @throws \Exception
1082
+     */
1083
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info) {
1084
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1085
+        $missing = $dependencyAnalyzer->analyze($info);
1086
+        if (!empty($missing)) {
1087
+            $missingMsg = implode(PHP_EOL, $missing);
1088
+            throw new \Exception(
1089
+                $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1090
+                    [$info['name'], $missingMsg]
1091
+                )
1092
+            );
1093
+        }
1094
+    }
1095 1095
 }
Please login to merge, or discard this patch.
lib/private/App/AppManager.php 1 patch
Indentation   +411 added lines, -411 removed lines patch added patch discarded remove patch
@@ -44,415 +44,415 @@
 block discarded – undo
44 44
 
45 45
 class AppManager implements IAppManager {
46 46
 
47
-	/**
48
-	 * Apps with these types can not be enabled for certain groups only
49
-	 * @var string[]
50
-	 */
51
-	protected $protectedAppTypes = [
52
-		'filesystem',
53
-		'prelogin',
54
-		'authentication',
55
-		'logging',
56
-		'prevent_group_restriction',
57
-	];
58
-
59
-	/** @var IUserSession */
60
-	private $userSession;
61
-
62
-	/** @var AppConfig */
63
-	private $appConfig;
64
-
65
-	/** @var IGroupManager */
66
-	private $groupManager;
67
-
68
-	/** @var ICacheFactory */
69
-	private $memCacheFactory;
70
-
71
-	/** @var EventDispatcherInterface */
72
-	private $dispatcher;
73
-
74
-	/** @var string[] $appId => $enabled */
75
-	private $installedAppsCache;
76
-
77
-	/** @var string[] */
78
-	private $shippedApps;
79
-
80
-	/** @var string[] */
81
-	private $alwaysEnabled;
82
-
83
-	/** @var array */
84
-	private $appInfos = [];
85
-
86
-	/** @var array */
87
-	private $appVersions = [];
88
-
89
-	/**
90
-	 * @param IUserSession $userSession
91
-	 * @param AppConfig $appConfig
92
-	 * @param IGroupManager $groupManager
93
-	 * @param ICacheFactory $memCacheFactory
94
-	 * @param EventDispatcherInterface $dispatcher
95
-	 */
96
-	public function __construct(IUserSession $userSession,
97
-								AppConfig $appConfig,
98
-								IGroupManager $groupManager,
99
-								ICacheFactory $memCacheFactory,
100
-								EventDispatcherInterface $dispatcher) {
101
-		$this->userSession = $userSession;
102
-		$this->appConfig = $appConfig;
103
-		$this->groupManager = $groupManager;
104
-		$this->memCacheFactory = $memCacheFactory;
105
-		$this->dispatcher = $dispatcher;
106
-	}
107
-
108
-	/**
109
-	 * @return string[] $appId => $enabled
110
-	 */
111
-	private function getInstalledAppsValues() {
112
-		if (!$this->installedAppsCache) {
113
-			$values = $this->appConfig->getValues(false, 'enabled');
114
-
115
-			$alwaysEnabledApps = $this->getAlwaysEnabledApps();
116
-			foreach($alwaysEnabledApps as $appId) {
117
-				$values[$appId] = 'yes';
118
-			}
119
-
120
-			$this->installedAppsCache = array_filter($values, function ($value) {
121
-				return $value !== 'no';
122
-			});
123
-			ksort($this->installedAppsCache);
124
-		}
125
-		return $this->installedAppsCache;
126
-	}
127
-
128
-	/**
129
-	 * List all installed apps
130
-	 *
131
-	 * @return string[]
132
-	 */
133
-	public function getInstalledApps() {
134
-		return array_keys($this->getInstalledAppsValues());
135
-	}
136
-
137
-	/**
138
-	 * List all apps enabled for a user
139
-	 *
140
-	 * @param \OCP\IUser $user
141
-	 * @return string[]
142
-	 */
143
-	public function getEnabledAppsForUser(IUser $user) {
144
-		$apps = $this->getInstalledAppsValues();
145
-		$appsForUser = array_filter($apps, function ($enabled) use ($user) {
146
-			return $this->checkAppForUser($enabled, $user);
147
-		});
148
-		return array_keys($appsForUser);
149
-	}
150
-
151
-	/**
152
-	 * Check if an app is enabled for user
153
-	 *
154
-	 * @param string $appId
155
-	 * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
156
-	 * @return bool
157
-	 */
158
-	public function isEnabledForUser($appId, $user = null) {
159
-		if ($this->isAlwaysEnabled($appId)) {
160
-			return true;
161
-		}
162
-		if ($user === null) {
163
-			$user = $this->userSession->getUser();
164
-		}
165
-		$installedApps = $this->getInstalledAppsValues();
166
-		if (isset($installedApps[$appId])) {
167
-			return $this->checkAppForUser($installedApps[$appId], $user);
168
-		} else {
169
-			return false;
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * @param string $enabled
175
-	 * @param IUser $user
176
-	 * @return bool
177
-	 */
178
-	private function checkAppForUser($enabled, $user) {
179
-		if ($enabled === 'yes') {
180
-			return true;
181
-		} elseif ($user === null) {
182
-			return false;
183
-		} else {
184
-			if(empty($enabled)){
185
-				return false;
186
-			}
187
-
188
-			$groupIds = json_decode($enabled);
189
-
190
-			if (!is_array($groupIds)) {
191
-				$jsonError = json_last_error();
192
-				\OC::$server->getLogger()->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']);
193
-				return false;
194
-			}
195
-
196
-			$userGroups = $this->groupManager->getUserGroupIds($user);
197
-			foreach ($userGroups as $groupId) {
198
-				if (in_array($groupId, $groupIds, true)) {
199
-					return true;
200
-				}
201
-			}
202
-			return false;
203
-		}
204
-	}
205
-
206
-	/**
207
-	 * Check if an app is enabled in the instance
208
-	 *
209
-	 * Notice: This actually checks if the app is enabled and not only if it is installed.
210
-	 *
211
-	 * @param string $appId
212
-	 * @return bool
213
-	 */
214
-	public function isInstalled($appId) {
215
-		$installedApps = $this->getInstalledAppsValues();
216
-		return isset($installedApps[$appId]);
217
-	}
218
-
219
-	/**
220
-	 * Enable an app for every user
221
-	 *
222
-	 * @param string $appId
223
-	 * @throws AppPathNotFoundException
224
-	 */
225
-	public function enableApp($appId) {
226
-		// Check if app exists
227
-		$this->getAppPath($appId);
228
-
229
-		$this->installedAppsCache[$appId] = 'yes';
230
-		$this->appConfig->setValue($appId, 'enabled', 'yes');
231
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
232
-			ManagerEvent::EVENT_APP_ENABLE, $appId
233
-		));
234
-		$this->clearAppsCache();
235
-	}
236
-
237
-	/**
238
-	 * Whether a list of types contains a protected app type
239
-	 *
240
-	 * @param string[] $types
241
-	 * @return bool
242
-	 */
243
-	public function hasProtectedAppType($types) {
244
-		if (empty($types)) {
245
-			return false;
246
-		}
247
-
248
-		$protectedTypes = array_intersect($this->protectedAppTypes, $types);
249
-		return !empty($protectedTypes);
250
-	}
251
-
252
-	/**
253
-	 * Enable an app only for specific groups
254
-	 *
255
-	 * @param string $appId
256
-	 * @param \OCP\IGroup[] $groups
257
-	 * @throws \Exception if app can't be enabled for groups
258
-	 */
259
-	public function enableAppForGroups($appId, $groups) {
260
-		$info = $this->getAppInfo($appId);
261
-		if (!empty($info['types'])) {
262
-			$protectedTypes = array_intersect($this->protectedAppTypes, $info['types']);
263
-			if (!empty($protectedTypes)) {
264
-				throw new \Exception("$appId can't be enabled for groups.");
265
-			}
266
-		}
267
-
268
-		$groupIds = array_map(function ($group) {
269
-			/** @var \OCP\IGroup $group */
270
-			return $group->getGID();
271
-		}, $groups);
272
-		$this->installedAppsCache[$appId] = json_encode($groupIds);
273
-		$this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
274
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
275
-			ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
276
-		));
277
-		$this->clearAppsCache();
278
-	}
279
-
280
-	/**
281
-	 * Disable an app for every user
282
-	 *
283
-	 * @param string $appId
284
-	 * @throws \Exception if app can't be disabled
285
-	 */
286
-	public function disableApp($appId) {
287
-		if ($this->isAlwaysEnabled($appId)) {
288
-			throw new \Exception("$appId can't be disabled.");
289
-		}
290
-		unset($this->installedAppsCache[$appId]);
291
-		$this->appConfig->setValue($appId, 'enabled', 'no');
292
-
293
-		// run uninstall steps
294
-		$appData = $this->getAppInfo($appId);
295
-		if (!is_null($appData)) {
296
-			\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
297
-		}
298
-
299
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
300
-			ManagerEvent::EVENT_APP_DISABLE, $appId
301
-		));
302
-		$this->clearAppsCache();
303
-	}
304
-
305
-	/**
306
-	 * Get the directory for the given app.
307
-	 *
308
-	 * @param string $appId
309
-	 * @return string
310
-	 * @throws AppPathNotFoundException if app folder can't be found
311
-	 */
312
-	public function getAppPath($appId) {
313
-		$appPath = \OC_App::getAppPath($appId);
314
-		if($appPath === false) {
315
-			throw new AppPathNotFoundException('Could not find path for ' . $appId);
316
-		}
317
-		return $appPath;
318
-	}
319
-
320
-	/**
321
-	 * Clear the cached list of apps when enabling/disabling an app
322
-	 */
323
-	public function clearAppsCache() {
324
-		$settingsMemCache = $this->memCacheFactory->createDistributed('settings');
325
-		$settingsMemCache->clear('listApps');
326
-	}
327
-
328
-	/**
329
-	 * Returns a list of apps that need upgrade
330
-	 *
331
-	 * @param string $version Nextcloud version as array of version components
332
-	 * @return array list of app info from apps that need an upgrade
333
-	 *
334
-	 * @internal
335
-	 */
336
-	public function getAppsNeedingUpgrade($version) {
337
-		$appsToUpgrade = [];
338
-		$apps = $this->getInstalledApps();
339
-		foreach ($apps as $appId) {
340
-			$appInfo = $this->getAppInfo($appId);
341
-			$appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
342
-			if ($appDbVersion
343
-				&& isset($appInfo['version'])
344
-				&& version_compare($appInfo['version'], $appDbVersion, '>')
345
-				&& \OC_App::isAppCompatible($version, $appInfo)
346
-			) {
347
-				$appsToUpgrade[] = $appInfo;
348
-			}
349
-		}
350
-
351
-		return $appsToUpgrade;
352
-	}
353
-
354
-	/**
355
-	 * Returns the app information from "appinfo/info.xml".
356
-	 *
357
-	 * @param string $appId app id
358
-	 *
359
-	 * @param bool $path
360
-	 * @param null $lang
361
-	 * @return array app info
362
-	 */
363
-	public function getAppInfo(string $appId, bool $path = false, $lang = null) {
364
-		if ($path) {
365
-			$file = $appId;
366
-		} else {
367
-			if ($lang === null && isset($this->appInfos[$appId])) {
368
-				return $this->appInfos[$appId];
369
-			}
370
-			try {
371
-				$appPath = $this->getAppPath($appId);
372
-			} catch (AppPathNotFoundException $e) {
373
-				return null;
374
-			}
375
-			$file = $appPath . '/appinfo/info.xml';
376
-		}
377
-
378
-		$parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
379
-		$data = $parser->parse($file);
380
-
381
-		if (is_array($data)) {
382
-			$data = \OC_App::parseAppInfo($data, $lang);
383
-		}
384
-
385
-		if ($lang === null) {
386
-			$this->appInfos[$appId] = $data;
387
-		}
388
-
389
-		return $data;
390
-	}
391
-
392
-	public function getAppVersion(string $appId, bool $useCache = true): string {
393
-		if(!$useCache || !isset($this->appVersions[$appId])) {
394
-			$appInfo = \OC::$server->getAppManager()->getAppInfo($appId);
395
-			$this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
396
-		}
397
-		return $this->appVersions[$appId];
398
-	}
399
-
400
-	/**
401
-	 * Returns a list of apps incompatible with the given version
402
-	 *
403
-	 * @param string $version Nextcloud version as array of version components
404
-	 *
405
-	 * @return array list of app info from incompatible apps
406
-	 *
407
-	 * @internal
408
-	 */
409
-	public function getIncompatibleApps(string $version): array {
410
-		$apps = $this->getInstalledApps();
411
-		$incompatibleApps = array();
412
-		foreach ($apps as $appId) {
413
-			$info = $this->getAppInfo($appId);
414
-			if (!\OC_App::isAppCompatible($version, $info)) {
415
-				$incompatibleApps[] = $info;
416
-			}
417
-		}
418
-		return $incompatibleApps;
419
-	}
420
-
421
-	/**
422
-	 * @inheritdoc
423
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
424
-	 */
425
-	public function isShipped($appId) {
426
-		$this->loadShippedJson();
427
-		return in_array($appId, $this->shippedApps, true);
428
-	}
429
-
430
-	private function isAlwaysEnabled($appId) {
431
-		$alwaysEnabled = $this->getAlwaysEnabledApps();
432
-		return in_array($appId, $alwaysEnabled, true);
433
-	}
434
-
435
-	/**
436
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
437
-	 * @throws \Exception
438
-	 */
439
-	private function loadShippedJson() {
440
-		if ($this->shippedApps === null) {
441
-			$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
442
-			if (!file_exists($shippedJson)) {
443
-				throw new \Exception("File not found: $shippedJson");
444
-			}
445
-			$content = json_decode(file_get_contents($shippedJson), true);
446
-			$this->shippedApps = $content['shippedApps'];
447
-			$this->alwaysEnabled = $content['alwaysEnabled'];
448
-		}
449
-	}
450
-
451
-	/**
452
-	 * @inheritdoc
453
-	 */
454
-	public function getAlwaysEnabledApps() {
455
-		$this->loadShippedJson();
456
-		return $this->alwaysEnabled;
457
-	}
47
+    /**
48
+     * Apps with these types can not be enabled for certain groups only
49
+     * @var string[]
50
+     */
51
+    protected $protectedAppTypes = [
52
+        'filesystem',
53
+        'prelogin',
54
+        'authentication',
55
+        'logging',
56
+        'prevent_group_restriction',
57
+    ];
58
+
59
+    /** @var IUserSession */
60
+    private $userSession;
61
+
62
+    /** @var AppConfig */
63
+    private $appConfig;
64
+
65
+    /** @var IGroupManager */
66
+    private $groupManager;
67
+
68
+    /** @var ICacheFactory */
69
+    private $memCacheFactory;
70
+
71
+    /** @var EventDispatcherInterface */
72
+    private $dispatcher;
73
+
74
+    /** @var string[] $appId => $enabled */
75
+    private $installedAppsCache;
76
+
77
+    /** @var string[] */
78
+    private $shippedApps;
79
+
80
+    /** @var string[] */
81
+    private $alwaysEnabled;
82
+
83
+    /** @var array */
84
+    private $appInfos = [];
85
+
86
+    /** @var array */
87
+    private $appVersions = [];
88
+
89
+    /**
90
+     * @param IUserSession $userSession
91
+     * @param AppConfig $appConfig
92
+     * @param IGroupManager $groupManager
93
+     * @param ICacheFactory $memCacheFactory
94
+     * @param EventDispatcherInterface $dispatcher
95
+     */
96
+    public function __construct(IUserSession $userSession,
97
+                                AppConfig $appConfig,
98
+                                IGroupManager $groupManager,
99
+                                ICacheFactory $memCacheFactory,
100
+                                EventDispatcherInterface $dispatcher) {
101
+        $this->userSession = $userSession;
102
+        $this->appConfig = $appConfig;
103
+        $this->groupManager = $groupManager;
104
+        $this->memCacheFactory = $memCacheFactory;
105
+        $this->dispatcher = $dispatcher;
106
+    }
107
+
108
+    /**
109
+     * @return string[] $appId => $enabled
110
+     */
111
+    private function getInstalledAppsValues() {
112
+        if (!$this->installedAppsCache) {
113
+            $values = $this->appConfig->getValues(false, 'enabled');
114
+
115
+            $alwaysEnabledApps = $this->getAlwaysEnabledApps();
116
+            foreach($alwaysEnabledApps as $appId) {
117
+                $values[$appId] = 'yes';
118
+            }
119
+
120
+            $this->installedAppsCache = array_filter($values, function ($value) {
121
+                return $value !== 'no';
122
+            });
123
+            ksort($this->installedAppsCache);
124
+        }
125
+        return $this->installedAppsCache;
126
+    }
127
+
128
+    /**
129
+     * List all installed apps
130
+     *
131
+     * @return string[]
132
+     */
133
+    public function getInstalledApps() {
134
+        return array_keys($this->getInstalledAppsValues());
135
+    }
136
+
137
+    /**
138
+     * List all apps enabled for a user
139
+     *
140
+     * @param \OCP\IUser $user
141
+     * @return string[]
142
+     */
143
+    public function getEnabledAppsForUser(IUser $user) {
144
+        $apps = $this->getInstalledAppsValues();
145
+        $appsForUser = array_filter($apps, function ($enabled) use ($user) {
146
+            return $this->checkAppForUser($enabled, $user);
147
+        });
148
+        return array_keys($appsForUser);
149
+    }
150
+
151
+    /**
152
+     * Check if an app is enabled for user
153
+     *
154
+     * @param string $appId
155
+     * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
156
+     * @return bool
157
+     */
158
+    public function isEnabledForUser($appId, $user = null) {
159
+        if ($this->isAlwaysEnabled($appId)) {
160
+            return true;
161
+        }
162
+        if ($user === null) {
163
+            $user = $this->userSession->getUser();
164
+        }
165
+        $installedApps = $this->getInstalledAppsValues();
166
+        if (isset($installedApps[$appId])) {
167
+            return $this->checkAppForUser($installedApps[$appId], $user);
168
+        } else {
169
+            return false;
170
+        }
171
+    }
172
+
173
+    /**
174
+     * @param string $enabled
175
+     * @param IUser $user
176
+     * @return bool
177
+     */
178
+    private function checkAppForUser($enabled, $user) {
179
+        if ($enabled === 'yes') {
180
+            return true;
181
+        } elseif ($user === null) {
182
+            return false;
183
+        } else {
184
+            if(empty($enabled)){
185
+                return false;
186
+            }
187
+
188
+            $groupIds = json_decode($enabled);
189
+
190
+            if (!is_array($groupIds)) {
191
+                $jsonError = json_last_error();
192
+                \OC::$server->getLogger()->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']);
193
+                return false;
194
+            }
195
+
196
+            $userGroups = $this->groupManager->getUserGroupIds($user);
197
+            foreach ($userGroups as $groupId) {
198
+                if (in_array($groupId, $groupIds, true)) {
199
+                    return true;
200
+                }
201
+            }
202
+            return false;
203
+        }
204
+    }
205
+
206
+    /**
207
+     * Check if an app is enabled in the instance
208
+     *
209
+     * Notice: This actually checks if the app is enabled and not only if it is installed.
210
+     *
211
+     * @param string $appId
212
+     * @return bool
213
+     */
214
+    public function isInstalled($appId) {
215
+        $installedApps = $this->getInstalledAppsValues();
216
+        return isset($installedApps[$appId]);
217
+    }
218
+
219
+    /**
220
+     * Enable an app for every user
221
+     *
222
+     * @param string $appId
223
+     * @throws AppPathNotFoundException
224
+     */
225
+    public function enableApp($appId) {
226
+        // Check if app exists
227
+        $this->getAppPath($appId);
228
+
229
+        $this->installedAppsCache[$appId] = 'yes';
230
+        $this->appConfig->setValue($appId, 'enabled', 'yes');
231
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
232
+            ManagerEvent::EVENT_APP_ENABLE, $appId
233
+        ));
234
+        $this->clearAppsCache();
235
+    }
236
+
237
+    /**
238
+     * Whether a list of types contains a protected app type
239
+     *
240
+     * @param string[] $types
241
+     * @return bool
242
+     */
243
+    public function hasProtectedAppType($types) {
244
+        if (empty($types)) {
245
+            return false;
246
+        }
247
+
248
+        $protectedTypes = array_intersect($this->protectedAppTypes, $types);
249
+        return !empty($protectedTypes);
250
+    }
251
+
252
+    /**
253
+     * Enable an app only for specific groups
254
+     *
255
+     * @param string $appId
256
+     * @param \OCP\IGroup[] $groups
257
+     * @throws \Exception if app can't be enabled for groups
258
+     */
259
+    public function enableAppForGroups($appId, $groups) {
260
+        $info = $this->getAppInfo($appId);
261
+        if (!empty($info['types'])) {
262
+            $protectedTypes = array_intersect($this->protectedAppTypes, $info['types']);
263
+            if (!empty($protectedTypes)) {
264
+                throw new \Exception("$appId can't be enabled for groups.");
265
+            }
266
+        }
267
+
268
+        $groupIds = array_map(function ($group) {
269
+            /** @var \OCP\IGroup $group */
270
+            return $group->getGID();
271
+        }, $groups);
272
+        $this->installedAppsCache[$appId] = json_encode($groupIds);
273
+        $this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
274
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
275
+            ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
276
+        ));
277
+        $this->clearAppsCache();
278
+    }
279
+
280
+    /**
281
+     * Disable an app for every user
282
+     *
283
+     * @param string $appId
284
+     * @throws \Exception if app can't be disabled
285
+     */
286
+    public function disableApp($appId) {
287
+        if ($this->isAlwaysEnabled($appId)) {
288
+            throw new \Exception("$appId can't be disabled.");
289
+        }
290
+        unset($this->installedAppsCache[$appId]);
291
+        $this->appConfig->setValue($appId, 'enabled', 'no');
292
+
293
+        // run uninstall steps
294
+        $appData = $this->getAppInfo($appId);
295
+        if (!is_null($appData)) {
296
+            \OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
297
+        }
298
+
299
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
300
+            ManagerEvent::EVENT_APP_DISABLE, $appId
301
+        ));
302
+        $this->clearAppsCache();
303
+    }
304
+
305
+    /**
306
+     * Get the directory for the given app.
307
+     *
308
+     * @param string $appId
309
+     * @return string
310
+     * @throws AppPathNotFoundException if app folder can't be found
311
+     */
312
+    public function getAppPath($appId) {
313
+        $appPath = \OC_App::getAppPath($appId);
314
+        if($appPath === false) {
315
+            throw new AppPathNotFoundException('Could not find path for ' . $appId);
316
+        }
317
+        return $appPath;
318
+    }
319
+
320
+    /**
321
+     * Clear the cached list of apps when enabling/disabling an app
322
+     */
323
+    public function clearAppsCache() {
324
+        $settingsMemCache = $this->memCacheFactory->createDistributed('settings');
325
+        $settingsMemCache->clear('listApps');
326
+    }
327
+
328
+    /**
329
+     * Returns a list of apps that need upgrade
330
+     *
331
+     * @param string $version Nextcloud version as array of version components
332
+     * @return array list of app info from apps that need an upgrade
333
+     *
334
+     * @internal
335
+     */
336
+    public function getAppsNeedingUpgrade($version) {
337
+        $appsToUpgrade = [];
338
+        $apps = $this->getInstalledApps();
339
+        foreach ($apps as $appId) {
340
+            $appInfo = $this->getAppInfo($appId);
341
+            $appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
342
+            if ($appDbVersion
343
+                && isset($appInfo['version'])
344
+                && version_compare($appInfo['version'], $appDbVersion, '>')
345
+                && \OC_App::isAppCompatible($version, $appInfo)
346
+            ) {
347
+                $appsToUpgrade[] = $appInfo;
348
+            }
349
+        }
350
+
351
+        return $appsToUpgrade;
352
+    }
353
+
354
+    /**
355
+     * Returns the app information from "appinfo/info.xml".
356
+     *
357
+     * @param string $appId app id
358
+     *
359
+     * @param bool $path
360
+     * @param null $lang
361
+     * @return array app info
362
+     */
363
+    public function getAppInfo(string $appId, bool $path = false, $lang = null) {
364
+        if ($path) {
365
+            $file = $appId;
366
+        } else {
367
+            if ($lang === null && isset($this->appInfos[$appId])) {
368
+                return $this->appInfos[$appId];
369
+            }
370
+            try {
371
+                $appPath = $this->getAppPath($appId);
372
+            } catch (AppPathNotFoundException $e) {
373
+                return null;
374
+            }
375
+            $file = $appPath . '/appinfo/info.xml';
376
+        }
377
+
378
+        $parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
379
+        $data = $parser->parse($file);
380
+
381
+        if (is_array($data)) {
382
+            $data = \OC_App::parseAppInfo($data, $lang);
383
+        }
384
+
385
+        if ($lang === null) {
386
+            $this->appInfos[$appId] = $data;
387
+        }
388
+
389
+        return $data;
390
+    }
391
+
392
+    public function getAppVersion(string $appId, bool $useCache = true): string {
393
+        if(!$useCache || !isset($this->appVersions[$appId])) {
394
+            $appInfo = \OC::$server->getAppManager()->getAppInfo($appId);
395
+            $this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
396
+        }
397
+        return $this->appVersions[$appId];
398
+    }
399
+
400
+    /**
401
+     * Returns a list of apps incompatible with the given version
402
+     *
403
+     * @param string $version Nextcloud version as array of version components
404
+     *
405
+     * @return array list of app info from incompatible apps
406
+     *
407
+     * @internal
408
+     */
409
+    public function getIncompatibleApps(string $version): array {
410
+        $apps = $this->getInstalledApps();
411
+        $incompatibleApps = array();
412
+        foreach ($apps as $appId) {
413
+            $info = $this->getAppInfo($appId);
414
+            if (!\OC_App::isAppCompatible($version, $info)) {
415
+                $incompatibleApps[] = $info;
416
+            }
417
+        }
418
+        return $incompatibleApps;
419
+    }
420
+
421
+    /**
422
+     * @inheritdoc
423
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
424
+     */
425
+    public function isShipped($appId) {
426
+        $this->loadShippedJson();
427
+        return in_array($appId, $this->shippedApps, true);
428
+    }
429
+
430
+    private function isAlwaysEnabled($appId) {
431
+        $alwaysEnabled = $this->getAlwaysEnabledApps();
432
+        return in_array($appId, $alwaysEnabled, true);
433
+    }
434
+
435
+    /**
436
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
437
+     * @throws \Exception
438
+     */
439
+    private function loadShippedJson() {
440
+        if ($this->shippedApps === null) {
441
+            $shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
442
+            if (!file_exists($shippedJson)) {
443
+                throw new \Exception("File not found: $shippedJson");
444
+            }
445
+            $content = json_decode(file_get_contents($shippedJson), true);
446
+            $this->shippedApps = $content['shippedApps'];
447
+            $this->alwaysEnabled = $content['alwaysEnabled'];
448
+        }
449
+    }
450
+
451
+    /**
452
+     * @inheritdoc
453
+     */
454
+    public function getAlwaysEnabledApps() {
455
+        $this->loadShippedJson();
456
+        return $this->alwaysEnabled;
457
+    }
458 458
 }
Please login to merge, or discard this patch.
lib/public/App/IAppManager.php 1 patch
Indentation   +120 added lines, -120 removed lines patch added patch discarded remove patch
@@ -37,124 +37,124 @@
 block discarded – undo
37 37
  */
38 38
 interface IAppManager {
39 39
 
40
-	/**
41
-	 * Returns the app information from "appinfo/info.xml".
42
-	 *
43
-	 * @param string $appId
44
-	 * @return mixed
45
-	 * @since 14.0.0
46
-	 */
47
-	public function getAppInfo(string $appId, bool $path = false, $lang = null);
48
-
49
-	/**
50
-	 * Returns the app information from "appinfo/info.xml".
51
-	 *
52
-	 * @param string $appId
53
-	 * @param bool $useCache
54
-	 * @return string
55
-	 * @since 14.0.0
56
-	 */
57
-	public function getAppVersion(string $appId, bool $useCache = true): string;
58
-
59
-	/**
60
-	 * Check if an app is enabled for user
61
-	 *
62
-	 * @param string $appId
63
-	 * @param \OCP\IUser $user (optional) if not defined, the currently loggedin user will be used
64
-	 * @return bool
65
-	 * @since 8.0.0
66
-	 */
67
-	public function isEnabledForUser($appId, $user = null);
68
-
69
-	/**
70
-	 * Check if an app is enabled in the instance
71
-	 *
72
-	 * Notice: This actually checks if the app is enabled and not only if it is installed.
73
-	 *
74
-	 * @param string $appId
75
-	 * @return bool
76
-	 * @since 8.0.0
77
-	 */
78
-	public function isInstalled($appId);
79
-
80
-	/**
81
-	 * Enable an app for every user
82
-	 *
83
-	 * @param string $appId
84
-	 * @throws AppPathNotFoundException
85
-	 * @since 8.0.0
86
-	 */
87
-	public function enableApp($appId);
88
-
89
-	/**
90
-	 * Whether a list of types contains a protected app type
91
-	 *
92
-	 * @param string[] $types
93
-	 * @return bool
94
-	 * @since 12.0.0
95
-	 */
96
-	public function hasProtectedAppType($types);
97
-
98
-	/**
99
-	 * Enable an app only for specific groups
100
-	 *
101
-	 * @param string $appId
102
-	 * @param \OCP\IGroup[] $groups
103
-	 * @since 8.0.0
104
-	 */
105
-	public function enableAppForGroups($appId, $groups);
106
-
107
-	/**
108
-	 * Disable an app for every user
109
-	 *
110
-	 * @param string $appId
111
-	 * @since 8.0.0
112
-	 */
113
-	public function disableApp($appId);
114
-
115
-	/**
116
-	 * Get the directory for the given app.
117
-	 *
118
-	 * @param string $appId
119
-	 * @return string
120
-	 * @since 11.0.0
121
-	 * @throws AppPathNotFoundException
122
-	 */
123
-	public function getAppPath($appId);
124
-
125
-	/**
126
-	 * List all apps enabled for a user
127
-	 *
128
-	 * @param \OCP\IUser $user
129
-	 * @return string[]
130
-	 * @since 8.1.0
131
-	 */
132
-	public function getEnabledAppsForUser(IUser $user);
133
-
134
-	/**
135
-	 * List all installed apps
136
-	 *
137
-	 * @return string[]
138
-	 * @since 8.1.0
139
-	 */
140
-	public function getInstalledApps();
141
-
142
-	/**
143
-	 * Clear the cached list of apps when enabling/disabling an app
144
-	 * @since 8.1.0
145
-	 */
146
-	public function clearAppsCache();
147
-
148
-	/**
149
-	 * @param string $appId
150
-	 * @return boolean
151
-	 * @since 9.0.0
152
-	 */
153
-	public function isShipped($appId);
154
-
155
-	/**
156
-	 * @return string[]
157
-	 * @since 9.0.0
158
-	 */
159
-	public function getAlwaysEnabledApps();
40
+    /**
41
+     * Returns the app information from "appinfo/info.xml".
42
+     *
43
+     * @param string $appId
44
+     * @return mixed
45
+     * @since 14.0.0
46
+     */
47
+    public function getAppInfo(string $appId, bool $path = false, $lang = null);
48
+
49
+    /**
50
+     * Returns the app information from "appinfo/info.xml".
51
+     *
52
+     * @param string $appId
53
+     * @param bool $useCache
54
+     * @return string
55
+     * @since 14.0.0
56
+     */
57
+    public function getAppVersion(string $appId, bool $useCache = true): string;
58
+
59
+    /**
60
+     * Check if an app is enabled for user
61
+     *
62
+     * @param string $appId
63
+     * @param \OCP\IUser $user (optional) if not defined, the currently loggedin user will be used
64
+     * @return bool
65
+     * @since 8.0.0
66
+     */
67
+    public function isEnabledForUser($appId, $user = null);
68
+
69
+    /**
70
+     * Check if an app is enabled in the instance
71
+     *
72
+     * Notice: This actually checks if the app is enabled and not only if it is installed.
73
+     *
74
+     * @param string $appId
75
+     * @return bool
76
+     * @since 8.0.0
77
+     */
78
+    public function isInstalled($appId);
79
+
80
+    /**
81
+     * Enable an app for every user
82
+     *
83
+     * @param string $appId
84
+     * @throws AppPathNotFoundException
85
+     * @since 8.0.0
86
+     */
87
+    public function enableApp($appId);
88
+
89
+    /**
90
+     * Whether a list of types contains a protected app type
91
+     *
92
+     * @param string[] $types
93
+     * @return bool
94
+     * @since 12.0.0
95
+     */
96
+    public function hasProtectedAppType($types);
97
+
98
+    /**
99
+     * Enable an app only for specific groups
100
+     *
101
+     * @param string $appId
102
+     * @param \OCP\IGroup[] $groups
103
+     * @since 8.0.0
104
+     */
105
+    public function enableAppForGroups($appId, $groups);
106
+
107
+    /**
108
+     * Disable an app for every user
109
+     *
110
+     * @param string $appId
111
+     * @since 8.0.0
112
+     */
113
+    public function disableApp($appId);
114
+
115
+    /**
116
+     * Get the directory for the given app.
117
+     *
118
+     * @param string $appId
119
+     * @return string
120
+     * @since 11.0.0
121
+     * @throws AppPathNotFoundException
122
+     */
123
+    public function getAppPath($appId);
124
+
125
+    /**
126
+     * List all apps enabled for a user
127
+     *
128
+     * @param \OCP\IUser $user
129
+     * @return string[]
130
+     * @since 8.1.0
131
+     */
132
+    public function getEnabledAppsForUser(IUser $user);
133
+
134
+    /**
135
+     * List all installed apps
136
+     *
137
+     * @return string[]
138
+     * @since 8.1.0
139
+     */
140
+    public function getInstalledApps();
141
+
142
+    /**
143
+     * Clear the cached list of apps when enabling/disabling an app
144
+     * @since 8.1.0
145
+     */
146
+    public function clearAppsCache();
147
+
148
+    /**
149
+     * @param string $appId
150
+     * @return boolean
151
+     * @since 9.0.0
152
+     */
153
+    public function isShipped($appId);
154
+
155
+    /**
156
+     * @return string[]
157
+     * @since 9.0.0
158
+     */
159
+    public function getAlwaysEnabledApps();
160 160
 }
Please login to merge, or discard this patch.