Completed
Pull Request — master (#7264)
by Morris
16:57
created
lib/private/Installer.php 2 patches
Indentation   +560 added lines, -560 removed lines patch added patch discarded remove patch
@@ -57,564 +57,564 @@
 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
-		static $apps = null;
399
-		if ($apps === null) {
400
-			$apps = $appFetcher->get();
401
-		}
402
-
403
-		foreach($apps as $app) {
404
-			if($app['id'] === $appId) {
405
-				$currentVersion = OC_App::getAppVersion($appId);
406
-				$newestVersion = $app['releases'][0]['version'];
407
-				if (version_compare($newestVersion, $currentVersion, '>')) {
408
-					return $newestVersion;
409
-				} else {
410
-					return false;
411
-				}
412
-			}
413
-		}
414
-
415
-		return false;
416
-	}
417
-
418
-	/**
419
-	 * Check if app is already downloaded
420
-	 * @param string $name name of the application to remove
421
-	 * @return boolean
422
-	 *
423
-	 * The function will check if the app is already downloaded in the apps repository
424
-	 */
425
-	public function isDownloaded($name) {
426
-		foreach(\OC::$APPSROOTS as $dir) {
427
-			$dirToTest  = $dir['path'];
428
-			$dirToTest .= '/';
429
-			$dirToTest .= $name;
430
-			$dirToTest .= '/';
431
-
432
-			if (is_dir($dirToTest)) {
433
-				return true;
434
-			}
435
-		}
436
-
437
-		return false;
438
-	}
439
-
440
-	/**
441
-	 * Removes an app
442
-	 * @param string $appId ID of the application to remove
443
-	 * @return boolean
444
-	 *
445
-	 *
446
-	 * This function works as follows
447
-	 *   -# call uninstall repair steps
448
-	 *   -# removing the files
449
-	 *
450
-	 * The function will not delete preferences, tables and the configuration,
451
-	 * this has to be done by the function oc_app_uninstall().
452
-	 */
453
-	public function removeApp($appId) {
454
-		if($this->isDownloaded( $appId )) {
455
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
456
-			OC_Helper::rmdirr($appDir);
457
-			return true;
458
-		}else{
459
-			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
460
-
461
-			return false;
462
-		}
463
-
464
-	}
465
-
466
-	/**
467
-	 * Installs the app within the bundle and marks the bundle as installed
468
-	 *
469
-	 * @param Bundle $bundle
470
-	 * @throws \Exception If app could not get installed
471
-	 */
472
-	public function installAppBundle(Bundle $bundle) {
473
-		$appIds = $bundle->getAppIdentifiers();
474
-		foreach($appIds as $appId) {
475
-			if(!$this->isDownloaded($appId)) {
476
-				$this->downloadApp($appId);
477
-			}
478
-			$this->installApp($appId);
479
-			$app = new OC_App();
480
-			$app->enable($appId);
481
-		}
482
-		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
483
-		$bundles[] = $bundle->getIdentifier();
484
-		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
485
-	}
486
-
487
-	/**
488
-	 * Installs shipped apps
489
-	 *
490
-	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
491
-	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
492
-	 *                         working ownCloud at the end instead of an aborted update.
493
-	 * @return array Array of error messages (appid => Exception)
494
-	 */
495
-	public static function installShippedApps($softErrors = false) {
496
-		$errors = [];
497
-		foreach(\OC::$APPSROOTS as $app_dir) {
498
-			if($dir = opendir( $app_dir['path'] )) {
499
-				while( false !== ( $filename = readdir( $dir ))) {
500
-					if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
501
-						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
502
-							if(!Installer::isInstalled($filename)) {
503
-								$info=OC_App::getAppInfo($filename);
504
-								$enabled = isset($info['default_enable']);
505
-								if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
506
-									  && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
507
-									if ($softErrors) {
508
-										try {
509
-											Installer::installShippedApp($filename);
510
-										} catch (HintException $e) {
511
-											if ($e->getPrevious() instanceof TableExistsException) {
512
-												$errors[$filename] = $e;
513
-												continue;
514
-											}
515
-											throw $e;
516
-										}
517
-									} else {
518
-										Installer::installShippedApp($filename);
519
-									}
520
-									\OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes');
521
-								}
522
-							}
523
-						}
524
-					}
525
-				}
526
-				closedir( $dir );
527
-			}
528
-		}
529
-
530
-		return $errors;
531
-	}
532
-
533
-	/**
534
-	 * install an app already placed in the app folder
535
-	 * @param string $app id of the app to install
536
-	 * @return integer
537
-	 */
538
-	public static function installShippedApp($app) {
539
-		//install the database
540
-		$appPath = OC_App::getAppPath($app);
541
-		\OC_App::registerAutoloading($app, $appPath);
542
-
543
-		if(is_file("$appPath/appinfo/database.xml")) {
544
-			try {
545
-				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
546
-			} catch (TableExistsException $e) {
547
-				throw new HintException(
548
-					'Failed to enable app ' . $app,
549
-					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
550
-					0, $e
551
-				);
552
-			}
553
-		} else {
554
-			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
555
-			$ms->migrate();
556
-		}
557
-
558
-		//run appinfo/install.php
559
-		self::includeAppScript("$appPath/appinfo/install.php");
560
-
561
-		$info = OC_App::getAppInfo($app);
562
-		if (is_null($info)) {
563
-			return false;
564
-		}
565
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
566
-
567
-		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
568
-
569
-		$config = \OC::$server->getConfig();
570
-
571
-		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
572
-		if (array_key_exists('ocsid', $info)) {
573
-			$config->setAppValue($app, 'ocsid', $info['ocsid']);
574
-		}
575
-
576
-		//set remote/public handlers
577
-		foreach($info['remote'] as $name=>$path) {
578
-			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
579
-		}
580
-		foreach($info['public'] as $name=>$path) {
581
-			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
582
-		}
583
-
584
-		OC_App::setAppTypes($info['id']);
585
-
586
-		if(isset($info['settings']) && is_array($info['settings'])) {
587
-			// requires that autoloading was registered for the app,
588
-			// as happens before running the install.php some lines above
589
-			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
590
-		}
591
-
592
-		return $info['id'];
593
-	}
594
-
595
-	/**
596
-	 * check the code of an app with some static code checks
597
-	 * @param string $folder the folder of the app to check
598
-	 * @return boolean true for app is o.k. and false for app is not o.k.
599
-	 */
600
-	public static function checkCode($folder) {
601
-		// is the code checker enabled?
602
-		if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
603
-			return true;
604
-		}
605
-
606
-		$codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck()));
607
-		$errors = $codeChecker->analyseFolder(basename($folder), $folder);
608
-
609
-		return empty($errors);
610
-	}
611
-
612
-	/**
613
-	 * @param string $script
614
-	 */
615
-	private static function includeAppScript($script) {
616
-		if ( file_exists($script) ){
617
-			include $script;
618
-		}
619
-	}
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
+        static $apps = null;
399
+        if ($apps === null) {
400
+            $apps = $appFetcher->get();
401
+        }
402
+
403
+        foreach($apps as $app) {
404
+            if($app['id'] === $appId) {
405
+                $currentVersion = OC_App::getAppVersion($appId);
406
+                $newestVersion = $app['releases'][0]['version'];
407
+                if (version_compare($newestVersion, $currentVersion, '>')) {
408
+                    return $newestVersion;
409
+                } else {
410
+                    return false;
411
+                }
412
+            }
413
+        }
414
+
415
+        return false;
416
+    }
417
+
418
+    /**
419
+     * Check if app is already downloaded
420
+     * @param string $name name of the application to remove
421
+     * @return boolean
422
+     *
423
+     * The function will check if the app is already downloaded in the apps repository
424
+     */
425
+    public function isDownloaded($name) {
426
+        foreach(\OC::$APPSROOTS as $dir) {
427
+            $dirToTest  = $dir['path'];
428
+            $dirToTest .= '/';
429
+            $dirToTest .= $name;
430
+            $dirToTest .= '/';
431
+
432
+            if (is_dir($dirToTest)) {
433
+                return true;
434
+            }
435
+        }
436
+
437
+        return false;
438
+    }
439
+
440
+    /**
441
+     * Removes an app
442
+     * @param string $appId ID of the application to remove
443
+     * @return boolean
444
+     *
445
+     *
446
+     * This function works as follows
447
+     *   -# call uninstall repair steps
448
+     *   -# removing the files
449
+     *
450
+     * The function will not delete preferences, tables and the configuration,
451
+     * this has to be done by the function oc_app_uninstall().
452
+     */
453
+    public function removeApp($appId) {
454
+        if($this->isDownloaded( $appId )) {
455
+            $appDir = OC_App::getInstallPath() . '/' . $appId;
456
+            OC_Helper::rmdirr($appDir);
457
+            return true;
458
+        }else{
459
+            \OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
460
+
461
+            return false;
462
+        }
463
+
464
+    }
465
+
466
+    /**
467
+     * Installs the app within the bundle and marks the bundle as installed
468
+     *
469
+     * @param Bundle $bundle
470
+     * @throws \Exception If app could not get installed
471
+     */
472
+    public function installAppBundle(Bundle $bundle) {
473
+        $appIds = $bundle->getAppIdentifiers();
474
+        foreach($appIds as $appId) {
475
+            if(!$this->isDownloaded($appId)) {
476
+                $this->downloadApp($appId);
477
+            }
478
+            $this->installApp($appId);
479
+            $app = new OC_App();
480
+            $app->enable($appId);
481
+        }
482
+        $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
483
+        $bundles[] = $bundle->getIdentifier();
484
+        $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
485
+    }
486
+
487
+    /**
488
+     * Installs shipped apps
489
+     *
490
+     * This function installs all apps found in the 'apps' directory that should be enabled by default;
491
+     * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
492
+     *                         working ownCloud at the end instead of an aborted update.
493
+     * @return array Array of error messages (appid => Exception)
494
+     */
495
+    public static function installShippedApps($softErrors = false) {
496
+        $errors = [];
497
+        foreach(\OC::$APPSROOTS as $app_dir) {
498
+            if($dir = opendir( $app_dir['path'] )) {
499
+                while( false !== ( $filename = readdir( $dir ))) {
500
+                    if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
501
+                        if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
502
+                            if(!Installer::isInstalled($filename)) {
503
+                                $info=OC_App::getAppInfo($filename);
504
+                                $enabled = isset($info['default_enable']);
505
+                                if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
506
+                                      && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
507
+                                    if ($softErrors) {
508
+                                        try {
509
+                                            Installer::installShippedApp($filename);
510
+                                        } catch (HintException $e) {
511
+                                            if ($e->getPrevious() instanceof TableExistsException) {
512
+                                                $errors[$filename] = $e;
513
+                                                continue;
514
+                                            }
515
+                                            throw $e;
516
+                                        }
517
+                                    } else {
518
+                                        Installer::installShippedApp($filename);
519
+                                    }
520
+                                    \OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes');
521
+                                }
522
+                            }
523
+                        }
524
+                    }
525
+                }
526
+                closedir( $dir );
527
+            }
528
+        }
529
+
530
+        return $errors;
531
+    }
532
+
533
+    /**
534
+     * install an app already placed in the app folder
535
+     * @param string $app id of the app to install
536
+     * @return integer
537
+     */
538
+    public static function installShippedApp($app) {
539
+        //install the database
540
+        $appPath = OC_App::getAppPath($app);
541
+        \OC_App::registerAutoloading($app, $appPath);
542
+
543
+        if(is_file("$appPath/appinfo/database.xml")) {
544
+            try {
545
+                OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
546
+            } catch (TableExistsException $e) {
547
+                throw new HintException(
548
+                    'Failed to enable app ' . $app,
549
+                    'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
550
+                    0, $e
551
+                );
552
+            }
553
+        } else {
554
+            $ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
555
+            $ms->migrate();
556
+        }
557
+
558
+        //run appinfo/install.php
559
+        self::includeAppScript("$appPath/appinfo/install.php");
560
+
561
+        $info = OC_App::getAppInfo($app);
562
+        if (is_null($info)) {
563
+            return false;
564
+        }
565
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
566
+
567
+        OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
568
+
569
+        $config = \OC::$server->getConfig();
570
+
571
+        $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
572
+        if (array_key_exists('ocsid', $info)) {
573
+            $config->setAppValue($app, 'ocsid', $info['ocsid']);
574
+        }
575
+
576
+        //set remote/public handlers
577
+        foreach($info['remote'] as $name=>$path) {
578
+            $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
579
+        }
580
+        foreach($info['public'] as $name=>$path) {
581
+            $config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
582
+        }
583
+
584
+        OC_App::setAppTypes($info['id']);
585
+
586
+        if(isset($info['settings']) && is_array($info['settings'])) {
587
+            // requires that autoloading was registered for the app,
588
+            // as happens before running the install.php some lines above
589
+            \OC::$server->getSettingsManager()->setupSettings($info['settings']);
590
+        }
591
+
592
+        return $info['id'];
593
+    }
594
+
595
+    /**
596
+     * check the code of an app with some static code checks
597
+     * @param string $folder the folder of the app to check
598
+     * @return boolean true for app is o.k. and false for app is not o.k.
599
+     */
600
+    public static function checkCode($folder) {
601
+        // is the code checker enabled?
602
+        if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
603
+            return true;
604
+        }
605
+
606
+        $codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck()));
607
+        $errors = $codeChecker->analyseFolder(basename($folder), $folder);
608
+
609
+        return empty($errors);
610
+    }
611
+
612
+    /**
613
+     * @param string $script
614
+     */
615
+    private static function includeAppScript($script) {
616
+        if ( file_exists($script) ){
617
+            include $script;
618
+        }
619
+    }
620 620
 }
Please login to merge, or discard this patch.
Spacing   +54 added lines, -54 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);
@@ -400,8 +400,8 @@  discard block
 block discarded – undo
400 400
 			$apps = $appFetcher->get();
401 401
 		}
402 402
 
403
-		foreach($apps as $app) {
404
-			if($app['id'] === $appId) {
403
+		foreach ($apps as $app) {
404
+			if ($app['id'] === $appId) {
405 405
 				$currentVersion = OC_App::getAppVersion($appId);
406 406
 				$newestVersion = $app['releases'][0]['version'];
407 407
 				if (version_compare($newestVersion, $currentVersion, '>')) {
@@ -423,7 +423,7 @@  discard block
 block discarded – undo
423 423
 	 * The function will check if the app is already downloaded in the apps repository
424 424
 	 */
425 425
 	public function isDownloaded($name) {
426
-		foreach(\OC::$APPSROOTS as $dir) {
426
+		foreach (\OC::$APPSROOTS as $dir) {
427 427
 			$dirToTest  = $dir['path'];
428 428
 			$dirToTest .= '/';
429 429
 			$dirToTest .= $name;
@@ -451,11 +451,11 @@  discard block
 block discarded – undo
451 451
 	 * this has to be done by the function oc_app_uninstall().
452 452
 	 */
453 453
 	public function removeApp($appId) {
454
-		if($this->isDownloaded( $appId )) {
455
-			$appDir = OC_App::getInstallPath() . '/' . $appId;
454
+		if ($this->isDownloaded($appId)) {
455
+			$appDir = OC_App::getInstallPath().'/'.$appId;
456 456
 			OC_Helper::rmdirr($appDir);
457 457
 			return true;
458
-		}else{
458
+		} else {
459 459
 			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
460 460
 
461 461
 			return false;
@@ -471,8 +471,8 @@  discard block
 block discarded – undo
471 471
 	 */
472 472
 	public function installAppBundle(Bundle $bundle) {
473 473
 		$appIds = $bundle->getAppIdentifiers();
474
-		foreach($appIds as $appId) {
475
-			if(!$this->isDownloaded($appId)) {
474
+		foreach ($appIds as $appId) {
475
+			if (!$this->isDownloaded($appId)) {
476 476
 				$this->downloadApp($appId);
477 477
 			}
478 478
 			$this->installApp($appId);
@@ -494,13 +494,13 @@  discard block
 block discarded – undo
494 494
 	 */
495 495
 	public static function installShippedApps($softErrors = false) {
496 496
 		$errors = [];
497
-		foreach(\OC::$APPSROOTS as $app_dir) {
498
-			if($dir = opendir( $app_dir['path'] )) {
499
-				while( false !== ( $filename = readdir( $dir ))) {
500
-					if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
501
-						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
502
-							if(!Installer::isInstalled($filename)) {
503
-								$info=OC_App::getAppInfo($filename);
497
+		foreach (\OC::$APPSROOTS as $app_dir) {
498
+			if ($dir = opendir($app_dir['path'])) {
499
+				while (false !== ($filename = readdir($dir))) {
500
+					if (substr($filename, 0, 1) != '.' and is_dir($app_dir['path']."/$filename")) {
501
+						if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) {
502
+							if (!Installer::isInstalled($filename)) {
503
+								$info = OC_App::getAppInfo($filename);
504 504
 								$enabled = isset($info['default_enable']);
505 505
 								if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
506 506
 									  && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
@@ -523,7 +523,7 @@  discard block
 block discarded – undo
523 523
 						}
524 524
 					}
525 525
 				}
526
-				closedir( $dir );
526
+				closedir($dir);
527 527
 			}
528 528
 		}
529 529
 
@@ -540,12 +540,12 @@  discard block
 block discarded – undo
540 540
 		$appPath = OC_App::getAppPath($app);
541 541
 		\OC_App::registerAutoloading($app, $appPath);
542 542
 
543
-		if(is_file("$appPath/appinfo/database.xml")) {
543
+		if (is_file("$appPath/appinfo/database.xml")) {
544 544
 			try {
545 545
 				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
546 546
 			} catch (TableExistsException $e) {
547 547
 				throw new HintException(
548
-					'Failed to enable app ' . $app,
548
+					'Failed to enable app '.$app,
549 549
 					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
550 550
 					0, $e
551 551
 				);
@@ -574,16 +574,16 @@  discard block
 block discarded – undo
574 574
 		}
575 575
 
576 576
 		//set remote/public handlers
577
-		foreach($info['remote'] as $name=>$path) {
577
+		foreach ($info['remote'] as $name=>$path) {
578 578
 			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
579 579
 		}
580
-		foreach($info['public'] as $name=>$path) {
580
+		foreach ($info['public'] as $name=>$path) {
581 581
 			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
582 582
 		}
583 583
 
584 584
 		OC_App::setAppTypes($info['id']);
585 585
 
586
-		if(isset($info['settings']) && is_array($info['settings'])) {
586
+		if (isset($info['settings']) && is_array($info['settings'])) {
587 587
 			// requires that autoloading was registered for the app,
588 588
 			// as happens before running the install.php some lines above
589 589
 			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
@@ -599,7 +599,7 @@  discard block
 block discarded – undo
599 599
 	 */
600 600
 	public static function checkCode($folder) {
601 601
 		// is the code checker enabled?
602
-		if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
602
+		if (!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
603 603
 			return true;
604 604
 		}
605 605
 
@@ -613,7 +613,7 @@  discard block
 block discarded – undo
613 613
 	 * @param string $script
614 614
 	 */
615 615
 	private static function includeAppScript($script) {
616
-		if ( file_exists($script) ){
616
+		if (file_exists($script)) {
617 617
 			include $script;
618 618
 		}
619 619
 	}
Please login to merge, or discard this patch.