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