Completed
Push — master ( 3a6d16...44adca )
by Morris
07:26 queued 06:55
created

Installer::checkCode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 11
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\AppManager;
40
use OC\App\AppStore\Bundles\Bundle;
41
use OC\App\AppStore\Fetcher\AppFetcher;
42
use OC\App\CodeChecker\CodeChecker;
43
use OC\App\CodeChecker\EmptyCheck;
44
use OC\App\CodeChecker\PrivateCheck;
45
use OC\Archive\TAR;
46
use OC_App;
47
use OC_DB;
48
use OC_Helper;
49
use OCP\App\IAppManager;
50
use OCP\Http\Client\IClientService;
51
use OCP\IConfig;
52
use OCP\ILogger;
53
use OCP\ITempManager;
54
use phpseclib\File\X509;
55
56
/**
57
 * This class provides the functionality needed to install, update and remove apps
58
 */
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 View Code Duplication
	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 View Code Duplication
		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 View Code Duplication
		if (!\OC_App::isAppCompatible($version, $info)) {
0 ignored issues
show
Documentation introduced by
$version is of type array, but the function expects a string.

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...
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 View Code Duplication
		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)) {
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...
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 View Code Duplication
		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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::isUpdateAvailable(...pId, $this->appFetcher) 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...
191
			try {
192
				$this->downloadApp($appId);
193
			} catch (\Exception $e) {
194
				$this->logger->error($e->getMessage(), ['app' => 'core']);
195
				return false;
196
			}
197
			return OC_App::updateApp($appId);
198
		}
199
200
		return false;
201
	}
202
203
	/**
204
	 * Downloads an app and puts it into the app directory
205
	 *
206
	 * @param string $appId
207
	 *
208
	 * @throws \Exception If the installation was not successful
209
	 */
210
	public function downloadApp($appId) {
211
		$appId = strtolower($appId);
212
213
		$apps = $this->appFetcher->get();
214
		foreach($apps as $app) {
215
			if($app['id'] === $appId) {
216
				// Load the certificate
217
				$certificate = new X509();
218
				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
219
				$loadedCertificate = $certificate->loadX509($app['certificate']);
220
221
				// Verify if the certificate has been revoked
222
				$crl = new X509();
223
				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
224
				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
225
				if($crl->validateSignature() !== true) {
226
					throw new \Exception('Could not validate CRL signature');
227
				}
228
				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
229
				$revoked = $crl->getRevoked($csn);
230
				if ($revoked !== false) {
231
					throw new \Exception(
232
						sprintf(
233
							'Certificate "%s" has been revoked',
234
							$csn
235
						)
236
					);
237
				}
238
239
				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
240
				if($certificate->validateSignature() !== true) {
241
					throw new \Exception(
242
						sprintf(
243
							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
244
							$appId
245
						)
246
					);
247
				}
248
249
				// Verify if the certificate is issued for the requested app id
250
				$certInfo = openssl_x509_parse($app['certificate']);
251
				if(!isset($certInfo['subject']['CN'])) {
252
					throw new \Exception(
253
						sprintf(
254
							'App with id %s has a cert with no CN',
255
							$appId
256
						)
257
					);
258
				}
259
				if($certInfo['subject']['CN'] !== $appId) {
260
					throw new \Exception(
261
						sprintf(
262
							'App with id %s has a cert issued to %s',
263
							$appId,
264
							$certInfo['subject']['CN']
265
						)
266
					);
267
				}
268
269
				// Download the release
270
				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
271
				$client = $this->clientService->newClient();
272
				$client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
273
274
				// Check if the signature actually matches the downloaded content
275
				$certificate = openssl_get_publickey($app['certificate']);
276
				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
277
				openssl_free_key($certificate);
278
279
				if($verified === true) {
280
					// Seems to match, let's proceed
281
					$extractDir = $this->tempManager->getTemporaryFolder();
282
					$archive = new TAR($tempFile);
283
284
					if($archive) {
285
						if (!$archive->extract($extractDir)) {
286
							throw new \Exception(
287
								sprintf(
288
									'Could not extract app %s',
289
									$appId
290
								)
291
							);
292
						}
293
						$allFiles = scandir($extractDir);
294
						$folders = array_diff($allFiles, ['.', '..']);
295
						$folders = array_values($folders);
296
297
						if(count($folders) > 1) {
298
							throw new \Exception(
299
								sprintf(
300
									'Extracted app %s has more than 1 folder',
301
									$appId
302
								)
303
							);
304
						}
305
306
						// Check if appinfo/info.xml has the same app ID as well
307
						$loadEntities = libxml_disable_entity_loader(false);
308
						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
309
						libxml_disable_entity_loader($loadEntities);
310
						if((string)$xml->id !== $appId) {
311
							throw new \Exception(
312
								sprintf(
313
									'App for id %s has a wrong app ID in info.xml: %s',
314
									$appId,
315
									(string)$xml->id
316
								)
317
							);
318
						}
319
320
						// Check if the version is lower than before
321
						$currentVersion = OC_App::getAppVersion($appId);
322
						$newVersion = (string)$xml->version;
323
						if(version_compare($currentVersion, $newVersion) === 1) {
324
							throw new \Exception(
325
								sprintf(
326
									'App for id %s has version %s and tried to update to lower version %s',
327
									$appId,
328
									$currentVersion,
329
									$newVersion
330
								)
331
							);
332
						}
333
334
						$baseDir = OC_App::getInstallPath() . '/' . $appId;
335
						// Remove old app with the ID if existent
336
						OC_Helper::rmdirr($baseDir);
337
						// Move to app folder
338
						if(@mkdir($baseDir)) {
339
							$extractDir .= '/' . $folders[0];
340
							OC_Helper::copyr($extractDir, $baseDir);
341
						}
342
						OC_Helper::copyr($extractDir, $baseDir);
343
						OC_Helper::rmdirr($extractDir);
344
						return;
345
					} else {
346
						throw new \Exception(
347
							sprintf(
348
								'Could not extract app with ID %s to %s',
349
								$appId,
350
								$extractDir
351
							)
352
						);
353
					}
354
				} else {
355
					// Signature does not match
356
					throw new \Exception(
357
						sprintf(
358
							'App with id %s has invalid signature',
359
							$appId
360
						)
361
					);
362
				}
363
			}
364
		}
365
366
		throw new \Exception(
367
			sprintf(
368
				'Could not download app %s',
369
				$appId
370
			)
371
		);
372
	}
373
374
	/**
375
	 * Check if an update for the app is available
376
	 *
377
	 * @param string $appId
378
	 * @param AppFetcher $appFetcher
379
	 * @return string|false false or the version number of the update
380
	 */
381
	public static function isUpdateAvailable($appId,
382
									  AppFetcher $appFetcher) {
383
		static $isInstanceReadyForUpdates = null;
384
385
		if ($isInstanceReadyForUpdates === null) {
386
			$installPath = OC_App::getInstallPath();
387
			if ($installPath === false || $installPath === null) {
388
				$isInstanceReadyForUpdates = false;
389
			} else {
390
				$isInstanceReadyForUpdates = true;
391
			}
392
		}
393
394
		if ($isInstanceReadyForUpdates === false) {
395
			return false;
396
		}
397
398
		$apps = $appFetcher->get();
399
		foreach($apps as $app) {
400
			if($app['id'] === $appId) {
401
				$currentVersion = OC_App::getAppVersion($appId);
402
				$newestVersion = $app['releases'][0]['version'];
403
				if (version_compare($newestVersion, $currentVersion, '>')) {
404
					return $newestVersion;
405
				} else {
406
					return false;
407
				}
408
			}
409
		}
410
411
		return false;
412
	}
413
414
	/**
415
	 * Check if app is already downloaded
416
	 * @param string $name name of the application to remove
417
	 * @return boolean
418
	 *
419
	 * The function will check if the app is already downloaded in the apps repository
420
	 */
421
	public function isDownloaded($name) {
422
		foreach(\OC::$APPSROOTS as $dir) {
423
			$dirToTest  = $dir['path'];
424
			$dirToTest .= '/';
425
			$dirToTest .= $name;
426
			$dirToTest .= '/';
427
428
			if (is_dir($dirToTest)) {
429
				return true;
430
			}
431
		}
432
433
		return false;
434
	}
435
436
	/**
437
	 * Removes an app
438
	 * @param string $appId ID of the application to remove
439
	 * @return boolean
440
	 *
441
	 *
442
	 * This function works as follows
443
	 *   -# call uninstall repair steps
444
	 *   -# removing the files
445
	 *
446
	 * The function will not delete preferences, tables and the configuration,
447
	 * this has to be done by the function oc_app_uninstall().
448
	 */
449
	public function removeApp($appId) {
450
		if($this->isDownloaded( $appId )) {
451
			$appDir = OC_App::getInstallPath() . '/' . $appId;
452
			OC_Helper::rmdirr($appDir);
453
			return true;
454
		}else{
455
			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
456
457
			return false;
458
		}
459
460
	}
461
462
	/**
463
	 * Installs the app within the bundle and marks the bundle as installed
464
	 *
465
	 * @param Bundle $bundle
466
	 * @throws \Exception If app could not get installed
467
	 */
468
	public function installAppBundle(Bundle $bundle) {
469
		$appIds = $bundle->getAppIdentifiers();
470
		foreach($appIds as $appId) {
471
			if(!$this->isDownloaded($appId)) {
472
				$this->downloadApp($appId);
473
			}
474
			$this->installApp($appId);
475
			$app = new OC_App();
476
			$app->enable($appId);
477
		}
478
		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
479
		$bundles[] = $bundle->getIdentifier();
480
		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
481
	}
482
483
	/**
484
	 * Installs shipped apps
485
	 *
486
	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
487
	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
488
	 *                         working ownCloud at the end instead of an aborted update.
489
	 * @return array Array of error messages (appid => Exception)
490
	 */
491
	public static function installShippedApps($softErrors = false) {
492
		$errors = [];
493
		foreach(\OC::$APPSROOTS as $app_dir) {
494
			if($dir = opendir( $app_dir['path'] )) {
495
				while( false !== ( $filename = readdir( $dir ))) {
496
					if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
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...
497
						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
498
							if(!Installer::isInstalled($filename)) {
499
								$info=OC_App::getAppInfo($filename);
500
								$enabled = isset($info['default_enable']);
501
								if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
502
									  && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
503
									if ($softErrors) {
504
										try {
505
											Installer::installShippedApp($filename);
506
										} catch (HintException $e) {
507
											if ($e->getPrevious() instanceof TableExistsException) {
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...
508
												$errors[$filename] = $e;
509
												continue;
510
											}
511
											throw $e;
512
										}
513
									} else {
514
										Installer::installShippedApp($filename);
515
									}
516
									\OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes');
517
								}
518
							}
519
						}
520
					}
521
				}
522
				closedir( $dir );
523
			}
524
		}
525
526
		return $errors;
527
	}
528
529
	/**
530
	 * install an app already placed in the app folder
531
	 * @param string $app id of the app to install
532
	 * @return integer
533
	 */
534
	public static function installShippedApp($app) {
535
		//install the database
536
		$appPath = OC_App::getAppPath($app);
537
		\OC_App::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by \OC_App::getAppPath($app) on line 536 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...
538
539
		if(is_file("$appPath/appinfo/database.xml")) {
540
			try {
541
				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
542
			} 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...
543
				throw new HintException(
544
					'Failed to enable app ' . $app,
545
					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer noopener">support channels</a>.',
546
					0, $e
547
				);
548
			}
549
		} else {
550
			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
551
			$ms->migrate();
552
		}
553
554
		//run appinfo/install.php
555
		self::includeAppScript("$appPath/appinfo/install.php");
556
557
		$info = OC_App::getAppInfo($app);
558
		if (is_null($info)) {
559
			return false;
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...
560
		}
561
		\OC_App::setupBackgroundJobs($info['background-jobs']);
562
563
		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
564
565
		$config = \OC::$server->getConfig();
566
567
		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
568
		if (array_key_exists('ocsid', $info)) {
569
			$config->setAppValue($app, 'ocsid', $info['ocsid']);
570
		}
571
572
		//set remote/public handlers
573
		foreach($info['remote'] as $name=>$path) {
574
			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
575
		}
576
		foreach($info['public'] as $name=>$path) {
577
			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
578
		}
579
580
		OC_App::setAppTypes($info['id']);
581
582 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
583
			// requires that autoloading was registered for the app,
584
			// as happens before running the install.php some lines above
585
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
586
		}
587
588
		return $info['id'];
589
	}
590
591
	/**
592
	 * @param string $script
593
	 */
594
	private static function includeAppScript($script) {
595
		if ( file_exists($script) ){
596
			include $script;
597
		}
598
	}
599
}
600