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