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