Completed
Push — master ( f0d809...8ccb48 )
by Lukas
38:04 queued 19:05
created

Installer::isUpdateAvailable()   D

Complexity

Conditions 9
Paths 27

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 20
nc 27
nop 1
dl 0
loc 32
rs 4.909
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
	/** @var array - for caching the result of app fetcher */
71
	private $apps = null;
72
	/** @var bool|null - for caching the result of the ready status */
73
	private $isInstanceReadyForUpdates = null;
74
75
	/**
76
	 * @param AppFetcher $appFetcher
77
	 * @param IClientService $clientService
78
	 * @param ITempManager $tempManager
79
	 * @param ILogger $logger
80
	 * @param IConfig $config
81
	 */
82
	public function __construct(AppFetcher $appFetcher,
83
								IClientService $clientService,
84
								ITempManager $tempManager,
85
								ILogger $logger,
86
								IConfig $config) {
87
		$this->appFetcher = $appFetcher;
88
		$this->clientService = $clientService;
89
		$this->tempManager = $tempManager;
90
		$this->logger = $logger;
91
		$this->config = $config;
92
	}
93
94
	/**
95
	 * Installs an app that is located in one of the app folders already
96
	 *
97
	 * @param string $appId App to install
98
	 * @throws \Exception
99
	 * @return string app ID
100
	 */
101
	public function installApp($appId) {
102
		$app = \OC_App::findAppInDirectories($appId);
103
		if($app === false) {
104
			throw new \Exception('App not found in any app directory');
105
		}
106
107
		$basedir = $app['path'].'/'.$appId;
108
		$info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
109
110
		$l = \OC::$server->getL10N('core');
111
112 View Code Duplication
		if(!is_array($info)) {
113
			throw new \Exception(
114
				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
115
					[$info['name']]
116
				)
117
			);
118
		}
119
120
		$version = \OCP\Util::getVersion();
121 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...
122
			throw new \Exception(
123
				// TODO $l
124
				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
125
					[$info['name']]
126
				)
127
			);
128
		}
129
130
		// check for required dependencies
131
		\OC_App::checkAppDependencies($this->config, $l, $info);
132
		\OC_App::registerAutoloading($appId, $basedir);
133
134
		//install the database
135
		if(is_file($basedir.'/appinfo/database.xml')) {
136
			if (\OC::$server->getAppConfig()->getValue($info['id'], 'installed_version') === null) {
137
				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
138
			} else {
139
				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
140
			}
141
		} else {
142
			$ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
143
			$ms->migrate();
144
		}
145
146
		\OC_App::setupBackgroundJobs($info['background-jobs']);
147 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
148
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
149
		}
150
151
		//run appinfo/install.php
152
		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...
153
			self::includeAppScript($basedir . '/appinfo/install.php');
154
		}
155
156
		$appData = OC_App::getAppInfo($appId);
157
		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
158
159
		//set the installed version
160
		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
161
		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
162
163
		//set remote/public handlers
164
		foreach($info['remote'] as $name=>$path) {
165
			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
166
		}
167 View Code Duplication
		foreach($info['public'] as $name=>$path) {
168
			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
169
		}
170
171
		OC_App::setAppTypes($info['id']);
172
173
		return $info['id'];
174
	}
175
176
	/**
177
	 * @brief checks whether or not an app is installed
178
	 * @param string $app app
179
	 * @returns bool
180
	 *
181
	 * Checks whether or not an app is installed, i.e. registered in apps table.
182
	 */
183
	public static function isInstalled( $app ) {
184
		return (\OC::$server->getConfig()->getAppValue($app, "installed_version", null) !== null);
185
	}
186
187
	/**
188
	 * Updates the specified app from the appstore
189
	 *
190
	 * @param string $appId
191
	 * @return bool
192
	 */
193
	public function updateAppstoreApp($appId) {
194
		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...
195
			try {
196
				$this->downloadApp($appId);
197
			} catch (\Exception $e) {
198
				$this->logger->error($e->getMessage(), ['app' => 'core']);
199
				return false;
200
			}
201
			return OC_App::updateApp($appId);
202
		}
203
204
		return false;
205
	}
206
207
	/**
208
	 * Downloads an app and puts it into the app directory
209
	 *
210
	 * @param string $appId
211
	 *
212
	 * @throws \Exception If the installation was not successful
213
	 */
214
	public function downloadApp($appId) {
215
		$appId = strtolower($appId);
216
217
		$apps = $this->appFetcher->get();
218
		foreach($apps as $app) {
219
			if($app['id'] === $appId) {
220
				// Load the certificate
221
				$certificate = new X509();
222
				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
223
				$loadedCertificate = $certificate->loadX509($app['certificate']);
224
225
				// Verify if the certificate has been revoked
226
				$crl = new X509();
227
				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
228
				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
229
				if($crl->validateSignature() !== true) {
230
					throw new \Exception('Could not validate CRL signature');
231
				}
232
				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
233
				$revoked = $crl->getRevoked($csn);
234
				if ($revoked !== false) {
235
					throw new \Exception(
236
						sprintf(
237
							'Certificate "%s" has been revoked',
238
							$csn
239
						)
240
					);
241
				}
242
243
				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
244
				if($certificate->validateSignature() !== true) {
245
					throw new \Exception(
246
						sprintf(
247
							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
248
							$appId
249
						)
250
					);
251
				}
252
253
				// Verify if the certificate is issued for the requested app id
254
				$certInfo = openssl_x509_parse($app['certificate']);
255
				if(!isset($certInfo['subject']['CN'])) {
256
					throw new \Exception(
257
						sprintf(
258
							'App with id %s has a cert with no CN',
259
							$appId
260
						)
261
					);
262
				}
263
				if($certInfo['subject']['CN'] !== $appId) {
264
					throw new \Exception(
265
						sprintf(
266
							'App with id %s has a cert issued to %s',
267
							$appId,
268
							$certInfo['subject']['CN']
269
						)
270
					);
271
				}
272
273
				// Download the release
274
				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
275
				$client = $this->clientService->newClient();
276
				$client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
277
278
				// Check if the signature actually matches the downloaded content
279
				$certificate = openssl_get_publickey($app['certificate']);
280
				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
281
				openssl_free_key($certificate);
282
283
				if($verified === true) {
284
					// Seems to match, let's proceed
285
					$extractDir = $this->tempManager->getTemporaryFolder();
286
					$archive = new TAR($tempFile);
287
288
					if($archive) {
289
						if (!$archive->extract($extractDir)) {
290
							throw new \Exception(
291
								sprintf(
292
									'Could not extract app %s',
293
									$appId
294
								)
295
							);
296
						}
297
						$allFiles = scandir($extractDir);
298
						$folders = array_diff($allFiles, ['.', '..']);
299
						$folders = array_values($folders);
300
301
						if(count($folders) > 1) {
302
							throw new \Exception(
303
								sprintf(
304
									'Extracted app %s has more than 1 folder',
305
									$appId
306
								)
307
							);
308
						}
309
310
						// Check if appinfo/info.xml has the same app ID as well
311
						$loadEntities = libxml_disable_entity_loader(false);
312
						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
313
						libxml_disable_entity_loader($loadEntities);
314
						if((string)$xml->id !== $appId) {
315
							throw new \Exception(
316
								sprintf(
317
									'App for id %s has a wrong app ID in info.xml: %s',
318
									$appId,
319
									(string)$xml->id
320
								)
321
							);
322
						}
323
324
						// Check if the version is lower than before
325
						$currentVersion = OC_App::getAppVersion($appId);
326
						$newVersion = (string)$xml->version;
327
						if(version_compare($currentVersion, $newVersion) === 1) {
328
							throw new \Exception(
329
								sprintf(
330
									'App for id %s has version %s and tried to update to lower version %s',
331
									$appId,
332
									$currentVersion,
333
									$newVersion
334
								)
335
							);
336
						}
337
338
						$baseDir = OC_App::getInstallPath() . '/' . $appId;
339
						// Remove old app with the ID if existent
340
						OC_Helper::rmdirr($baseDir);
341
						// Move to app folder
342
						if(@mkdir($baseDir)) {
343
							$extractDir .= '/' . $folders[0];
344
							OC_Helper::copyr($extractDir, $baseDir);
345
						}
346
						OC_Helper::copyr($extractDir, $baseDir);
347
						OC_Helper::rmdirr($extractDir);
348
						return;
349
					} else {
350
						throw new \Exception(
351
							sprintf(
352
								'Could not extract app with ID %s to %s',
353
								$appId,
354
								$extractDir
355
							)
356
						);
357
					}
358
				} else {
359
					// Signature does not match
360
					throw new \Exception(
361
						sprintf(
362
							'App with id %s has invalid signature',
363
							$appId
364
						)
365
					);
366
				}
367
			}
368
		}
369
370
		throw new \Exception(
371
			sprintf(
372
				'Could not download app %s',
373
				$appId
374
			)
375
		);
376
	}
377
378
	/**
379
	 * Check if an update for the app is available
380
	 *
381
	 * @param string $appId
382
	 * @return string|false false or the version number of the update
383
	 */
384
	public function isUpdateAvailable($appId) {
385
		if ($this->isInstanceReadyForUpdates === null) {
386
			$installPath = OC_App::getInstallPath();
387
			if ($installPath === false || $installPath === null) {
388
				$this->isInstanceReadyForUpdates = false;
389
			} else {
390
				$this->isInstanceReadyForUpdates = true;
391
			}
392
		}
393
394
		if ($this->isInstanceReadyForUpdates === false) {
395
			return false;
396
		}
397
398
		if ($this->apps === null) {
399
			$apps = $this->appFetcher->get();
400
		}
401
402
		foreach($apps as $app) {
0 ignored issues
show
Bug introduced by
The variable $apps does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

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