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