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