Completed
Push — master ( f843b7...91b9c0 )
by Morris
100:29 queued 83:13
created

Installer::updateAppstoreApp()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2016, Lukas Reschke <[email protected]>
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Brice Maron <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Frank Karlitschek <[email protected]>
10
 * @author Georg Ehrke <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Kamil Domanski <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Robin Appelman <[email protected]>
16
 * @author root "root@oc.(none)"
17
 * @author Thomas Müller <[email protected]>
18
 * @author Thomas Tanghus <[email protected]>
19
 *
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
namespace OC;
37
38
use Doctrine\DBAL\Exception\TableExistsException;
39
use OC\App\AppStore\Bundles\Bundle;
40
use OC\App\AppStore\Fetcher\AppFetcher;
41
use OC\Archive\TAR;
42
use OC_App;
43
use OC_DB;
44
use OC_Helper;
45
use OCP\Http\Client\IClientService;
46
use OCP\IConfig;
47
use OCP\ILogger;
48
use OCP\ITempManager;
49
use phpseclib\File\X509;
50
51
/**
52
 * This class provides the functionality needed to install, update and remove apps
53
 */
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
		$version = implode('.', \OCP\Util::getVersion());
116
		if (!\OC_App::isAppCompatible($version, $info)) {
117
			throw new \Exception(
118
				// TODO $l
119
				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
120
					[$info['name']]
121
				)
122
			);
123
		}
124
125
		// check for required dependencies
126
		\OC_App::checkAppDependencies($this->config, $l, $info);
127
		\OC_App::registerAutoloading($appId, $basedir);
128
129
		//install the database
130
		if(is_file($basedir.'/appinfo/database.xml')) {
131
			if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) {
132
				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
133
			} else {
134
				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
135
			}
136
		} else {
137
			$ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
138
			$ms->migrate();
139
		}
140
141
		\OC_App::setupBackgroundJobs($info['background-jobs']);
142
143
		//run appinfo/install.php
144
		if(!isset($data['noinstall']) or $data['noinstall']==false) {
0 ignored issues
show
Bug introduced by
The variable $data seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
145
			self::includeAppScript($basedir . '/appinfo/install.php');
146
		}
147
148
		$appData = OC_App::getAppInfo($appId);
149
		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
150
151
		//set the installed version
152
		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
153
		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
154
155
		//set remote/public handlers
156
		foreach($info['remote'] as $name=>$path) {
157
			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
158
		}
159 View Code Duplication
		foreach($info['public'] as $name=>$path) {
160
			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
161
		}
162
163
		OC_App::setAppTypes($info['id']);
164
165
		return $info['id'];
166
	}
167
168
	/**
169
	 * Updates the specified app from the appstore
170
	 *
171
	 * @param string $appId
172
	 * @return bool
173
	 */
174
	public function updateAppstoreApp($appId) {
175
		if($this->isUpdateAvailable($appId)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->isUpdateAvailable($appId) of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
176
			try {
177
				$this->downloadApp($appId);
178
			} catch (\Exception $e) {
179
				$this->logger->logException($e, [
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
180
					'level' => \OCP\Util::ERROR,
181
					'app' => 'core',
182
				]);
183
				return false;
184
			}
185
			return OC_App::updateApp($appId);
186
		}
187
188
		return false;
189
	}
190
191
	/**
192
	 * Downloads an app and puts it into the app directory
193
	 *
194
	 * @param string $appId
195
	 *
196
	 * @throws \Exception If the installation was not successful
197
	 */
198
	public function downloadApp($appId) {
199
		$appId = strtolower($appId);
200
201
		$apps = $this->appFetcher->get();
202
		foreach($apps as $app) {
203
			if($app['id'] === $appId) {
204
				// Load the certificate
205
				$certificate = new X509();
206
				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
207
				$loadedCertificate = $certificate->loadX509($app['certificate']);
208
209
				// Verify if the certificate has been revoked
210
				$crl = new X509();
211
				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
212
				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
213
				if($crl->validateSignature() !== true) {
214
					throw new \Exception('Could not validate CRL signature');
215
				}
216
				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
217
				$revoked = $crl->getRevoked($csn);
218
				if ($revoked !== false) {
219
					throw new \Exception(
220
						sprintf(
221
							'Certificate "%s" has been revoked',
222
							$csn
223
						)
224
					);
225
				}
226
227
				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
228
				if($certificate->validateSignature() !== true) {
229
					throw new \Exception(
230
						sprintf(
231
							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
232
							$appId
233
						)
234
					);
235
				}
236
237
				// Verify if the certificate is issued for the requested app id
238
				$certInfo = openssl_x509_parse($app['certificate']);
239
				if(!isset($certInfo['subject']['CN'])) {
240
					throw new \Exception(
241
						sprintf(
242
							'App with id %s has a cert with no CN',
243
							$appId
244
						)
245
					);
246
				}
247
				if($certInfo['subject']['CN'] !== $appId) {
248
					throw new \Exception(
249
						sprintf(
250
							'App with id %s has a cert issued to %s',
251
							$appId,
252
							$certInfo['subject']['CN']
253
						)
254
					);
255
				}
256
257
				// Download the release
258
				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
259
				$client = $this->clientService->newClient();
260
				$client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
261
262
				// Check if the signature actually matches the downloaded content
263
				$certificate = openssl_get_publickey($app['certificate']);
264
				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
265
				openssl_free_key($certificate);
266
267
				if($verified === true) {
268
					// Seems to match, let's proceed
269
					$extractDir = $this->tempManager->getTemporaryFolder();
270
					$archive = new TAR($tempFile);
271
272
					if($archive) {
273
						if (!$archive->extract($extractDir)) {
274
							throw new \Exception(
275
								sprintf(
276
									'Could not extract app %s',
277
									$appId
278
								)
279
							);
280
						}
281
						$allFiles = scandir($extractDir);
282
						$folders = array_diff($allFiles, ['.', '..']);
283
						$folders = array_values($folders);
284
285
						if(count($folders) > 1) {
286
							throw new \Exception(
287
								sprintf(
288
									'Extracted app %s has more than 1 folder',
289
									$appId
290
								)
291
							);
292
						}
293
294
						// Check if appinfo/info.xml has the same app ID as well
295
						$loadEntities = libxml_disable_entity_loader(false);
296
						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
297
						libxml_disable_entity_loader($loadEntities);
298
						if((string)$xml->id !== $appId) {
299
							throw new \Exception(
300
								sprintf(
301
									'App for id %s has a wrong app ID in info.xml: %s',
302
									$appId,
303
									(string)$xml->id
304
								)
305
							);
306
						}
307
308
						// Check if the version is lower than before
309
						$currentVersion = OC_App::getAppVersion($appId);
310
						$newVersion = (string)$xml->version;
311
						if(version_compare($currentVersion, $newVersion) === 1) {
312
							throw new \Exception(
313
								sprintf(
314
									'App for id %s has version %s and tried to update to lower version %s',
315
									$appId,
316
									$currentVersion,
317
									$newVersion
318
								)
319
							);
320
						}
321
322
						$baseDir = OC_App::getInstallPath() . '/' . $appId;
323
						// Remove old app with the ID if existent
324
						OC_Helper::rmdirr($baseDir);
325
						// Move to app folder
326
						if(@mkdir($baseDir)) {
327
							$extractDir .= '/' . $folders[0];
328
							OC_Helper::copyr($extractDir, $baseDir);
329
						}
330
						OC_Helper::copyr($extractDir, $baseDir);
331
						OC_Helper::rmdirr($extractDir);
332
						return;
333
					} else {
334
						throw new \Exception(
335
							sprintf(
336
								'Could not extract app with ID %s to %s',
337
								$appId,
338
								$extractDir
339
							)
340
						);
341
					}
342
				} else {
343
					// Signature does not match
344
					throw new \Exception(
345
						sprintf(
346
							'App with id %s has invalid signature',
347
							$appId
348
						)
349
					);
350
				}
351
			}
352
		}
353
354
		throw new \Exception(
355
			sprintf(
356
				'Could not download app %s',
357
				$appId
358
			)
359
		);
360
	}
361
362
	/**
363
	 * Check if an update for the app is available
364
	 *
365
	 * @param string $appId
366
	 * @return string|false false or the version number of the update
367
	 */
368
	public function isUpdateAvailable($appId) {
369
		if ($this->isInstanceReadyForUpdates === null) {
370
			$installPath = OC_App::getInstallPath();
371
			if ($installPath === false || $installPath === null) {
372
				$this->isInstanceReadyForUpdates = false;
373
			} else {
374
				$this->isInstanceReadyForUpdates = true;
375
			}
376
		}
377
378
		if ($this->isInstanceReadyForUpdates === false) {
379
			return false;
380
		}
381
382
		if ($this->isInstalledFromGit($appId) === true) {
383
			return false;
384
		}
385
386
		if ($this->apps === null) {
387
			$this->apps = $this->appFetcher->get();
388
		}
389
390
		foreach($this->apps as $app) {
391
			if($app['id'] === $appId) {
392
				$currentVersion = OC_App::getAppVersion($appId);
393
				$newestVersion = $app['releases'][0]['version'];
394
				if (version_compare($newestVersion, $currentVersion, '>')) {
395
					return $newestVersion;
396
				} else {
397
					return false;
398
				}
399
			}
400
		}
401
402
		return false;
403
	}
404
405
	/**
406
	 * Check if app has been installed from git
407
	 * @param string $name name of the application to remove
0 ignored issues
show
Bug introduced by
There is no parameter named $name. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
408
	 * @return boolean
409
	 *
410
	 * The function will check if the path contains a .git folder
411
	 */
412
	private function isInstalledFromGit($appId) {
413
		$app = \OC_App::findAppInDirectories($appId);
414
		if($app === false) {
415
			return false;
416
		}
417
		$basedir = $app['path'].'/'.$appId;
418
		return file_exists($basedir.'/.git/');
419
	}
420
421
	/**
422
	 * Check if app is already downloaded
423
	 * @param string $name name of the application to remove
424
	 * @return boolean
425
	 *
426
	 * The function will check if the app is already downloaded in the apps repository
427
	 */
428
	public function isDownloaded($name) {
429
		foreach(\OC::$APPSROOTS as $dir) {
430
			$dirToTest  = $dir['path'];
431
			$dirToTest .= '/';
432
			$dirToTest .= $name;
433
			$dirToTest .= '/';
434
435
			if (is_dir($dirToTest)) {
436
				return true;
437
			}
438
		}
439
440
		return false;
441
	}
442
443
	/**
444
	 * Removes an app
445
	 * @param string $appId ID of the application to remove
446
	 * @return boolean
447
	 *
448
	 *
449
	 * This function works as follows
450
	 *   -# call uninstall repair steps
451
	 *   -# removing the files
452
	 *
453
	 * The function will not delete preferences, tables and the configuration,
454
	 * this has to be done by the function oc_app_uninstall().
455
	 */
456
	public function removeApp($appId) {
457
		if($this->isDownloaded( $appId )) {
458
			if (\OC::$server->getAppManager()->isShipped($appId)) {
459
				return false;
460
			}
461
			$appDir = OC_App::getInstallPath() . '/' . $appId;
462
			OC_Helper::rmdirr($appDir);
463
			return true;
464
		}else{
465
			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
466
467
			return false;
468
		}
469
470
	}
471
472
	/**
473
	 * Installs the app within the bundle and marks the bundle as installed
474
	 *
475
	 * @param Bundle $bundle
476
	 * @throws \Exception If app could not get installed
477
	 */
478
	public function installAppBundle(Bundle $bundle) {
479
		$appIds = $bundle->getAppIdentifiers();
480
		foreach($appIds as $appId) {
481
			if(!$this->isDownloaded($appId)) {
482
				$this->downloadApp($appId);
483
			}
484
			$this->installApp($appId);
485
			$app = new OC_App();
486
			$app->enable($appId);
487
		}
488
		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
489
		$bundles[] = $bundle->getIdentifier();
490
		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
491
	}
492
493
	/**
494
	 * Installs shipped apps
495
	 *
496
	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
497
	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
498
	 *                         working ownCloud at the end instead of an aborted update.
499
	 * @return array Array of error messages (appid => Exception)
500
	 */
501
	public static function installShippedApps($softErrors = false) {
502
		$appManager = \OC::$server->getAppManager();
503
		$config = \OC::$server->getConfig();
504
		$errors = [];
505
		foreach(\OC::$APPSROOTS as $app_dir) {
506
			if($dir = opendir( $app_dir['path'] )) {
507
				while( false !== ( $filename = readdir( $dir ))) {
508
					if( $filename[0] !== '.' and is_dir($app_dir['path']."/$filename") ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
509
						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
510
							if($config->getAppValue($filename, "installed_version", null) === null) {
511
								$info=OC_App::getAppInfo($filename);
512
								$enabled = isset($info['default_enable']);
513
								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
514
									  && $config->getAppValue($filename, 'enabled') !== 'no') {
515
									if ($softErrors) {
516
										try {
517
											Installer::installShippedApp($filename);
518
										} catch (HintException $e) {
519
											if ($e->getPrevious() instanceof TableExistsException) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Exception\TableExistsException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
520
												$errors[$filename] = $e;
521
												continue;
522
											}
523
											throw $e;
524
										}
525
									} else {
526
										Installer::installShippedApp($filename);
527
									}
528
									$config->setAppValue($filename, 'enabled', 'yes');
529
								}
530
							}
531
						}
532
					}
533
				}
534
				closedir( $dir );
535
			}
536
		}
537
538
		return $errors;
539
	}
540
541
	/**
542
	 * install an app already placed in the app folder
543
	 * @param string $app id of the app to install
544
	 * @return integer
545
	 */
546
	public static function installShippedApp($app) {
547
		//install the database
548
		$appPath = OC_App::getAppPath($app);
549
		\OC_App::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by \OC_App::getAppPath($app) on line 548 can also be of type false; however, OC_App::registerAutoloading() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
550
551
		if(is_file("$appPath/appinfo/database.xml")) {
552
			try {
553
				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
554
			} catch (TableExistsException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Exception\TableExistsException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
555
				throw new HintException(
556
					'Failed to enable app ' . $app,
557
					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
558
					0, $e
559
				);
560
			}
561
		} else {
562
			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
563
			$ms->migrate();
564
		}
565
566
		//run appinfo/install.php
567
		self::includeAppScript("$appPath/appinfo/install.php");
568
569
		$info = OC_App::getAppInfo($app);
570
		if (is_null($info)) {
571
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by OC\Installer::installShippedApp of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
572
		}
573
		\OC_App::setupBackgroundJobs($info['background-jobs']);
574
575
		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
576
577
		$config = \OC::$server->getConfig();
578
579
		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
580
		if (array_key_exists('ocsid', $info)) {
581
			$config->setAppValue($app, 'ocsid', $info['ocsid']);
582
		}
583
584
		//set remote/public handlers
585
		foreach($info['remote'] as $name=>$path) {
586
			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
587
		}
588
		foreach($info['public'] as $name=>$path) {
589
			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
590
		}
591
592
		OC_App::setAppTypes($info['id']);
593
594
		return $info['id'];
595
	}
596
597
	/**
598
	 * @param string $script
599
	 */
600
	private static function includeAppScript($script) {
601
		if ( file_exists($script) ){
602
			include $script;
603
		}
604
	}
605
}
606