Completed
Push — master ( ca5656...60398b )
by Morris
32:43 queued 16:28
created

Installer::isDownloaded()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 14
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 Bart Visscher <[email protected]>
8
 * @author Brice Maron <[email protected]>
9
 * @author Christian Weiske <[email protected]>
10
 * @author Christopher Schäpers <[email protected]>
11
 * @author Frank Karlitschek <[email protected]>
12
 * @author Georg Ehrke <[email protected]>
13
 * @author Jakob Sack <[email protected]>
14
 * @author Joas Schilling <[email protected]>
15
 * @author Jörn Friedrich Dreyer <[email protected]>
16
 * @author Kamil Domanski <[email protected]>
17
 * @author Lukas Reschke <[email protected]>
18
 * @author michag86 <[email protected]>
19
 * @author Morris Jobke <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author root <root@oc.(none)>
23
 * @author Thomas Müller <[email protected]>
24
 * @author Thomas Tanghus <[email protected]>
25
 *
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
namespace OC;
43
44
use Doctrine\DBAL\Exception\TableExistsException;
45
use OC\App\AppManager;
46
use OC\App\AppStore\Bundles\Bundle;
47
use OC\App\AppStore\Fetcher\AppFetcher;
48
use OC\App\CodeChecker\CodeChecker;
49
use OC\App\CodeChecker\EmptyCheck;
50
use OC\App\CodeChecker\PrivateCheck;
51
use OC\Archive\TAR;
52
use OC_App;
53
use OC_DB;
54
use OC_Helper;
55
use OCP\App\IAppManager;
56
use OCP\Http\Client\IClientService;
57
use OCP\IConfig;
58
use OCP\ILogger;
59
use OCP\ITempManager;
60
use phpseclib\File\X509;
61
62
/**
63
 * This class provides the functionality needed to install, update and remove apps
64
 */
65
class Installer {
66
	/** @var AppFetcher */
67
	private $appFetcher;
68
	/** @var IClientService */
69
	private $clientService;
70
	/** @var ITempManager */
71
	private $tempManager;
72
	/** @var ILogger */
73
	private $logger;
74
	/** @var IConfig */
75
	private $config;
76
77
	/**
78
	 * @param AppFetcher $appFetcher
79
	 * @param IClientService $clientService
80
	 * @param ITempManager $tempManager
81
	 * @param ILogger $logger
82
	 * @param IConfig $config
83
	 */
84 View Code Duplication
	public function __construct(AppFetcher $appFetcher,
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
85
								IClientService $clientService,
86
								ITempManager $tempManager,
87
								ILogger $logger,
88
								IConfig $config) {
89
		$this->appFetcher = $appFetcher;
90
		$this->clientService = $clientService;
91
		$this->tempManager = $tempManager;
92
		$this->logger = $logger;
93
		$this->config = $config;
94
	}
95
96
	/**
97
	 * Installs an app that is located in one of the app folders already
98
	 *
99
	 * @param string $appId App to install
100
	 * @throws \Exception
101
	 * @return string app ID
102
	 */
103
	public function installApp($appId) {
104
		$app = \OC_App::findAppInDirectories($appId);
105
		if($app === false) {
106
			throw new \Exception('App not found in any app directory');
107
		}
108
109
		$basedir = $app['path'].'/'.$appId;
110
		$info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true);
111
112
		$l = \OC::$server->getL10N('core');
113
114 View Code Duplication
		if(!is_array($info)) {
115
			throw new \Exception(
116
				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
117
					[$info['name']]
118
				)
119
			);
120
		}
121
122
		$version = \OCP\Util::getVersion();
123 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...
124
			throw new \Exception(
125
				// TODO $l
126
				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
127
					[$info['name']]
128
				)
129
			);
130
		}
131
132
		// check for required dependencies
133
		\OC_App::checkAppDependencies($this->config, $l, $info);
134
135
		//install the database
136
		if(is_file($basedir.'/appinfo/database.xml')) {
137
			if (\OC::$server->getAppConfig()->getValue($info['id'], 'installed_version') === null) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::getValue() has been deprecated with message: 8.0.0 use method getAppValue of \OCP\IConfig This function gets a value from the appconfig table. If the key does
not exist the default value will be returned

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
138
				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
139
			} else {
140
				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
141
			}
142
		} else {
143
			$ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
144
			$ms->migrate();
145
		}
146
147
		\OC_App::registerAutoloading($appId, $basedir);
148
		\OC_App::setupBackgroundJobs($info['background-jobs']);
149 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
150
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
151
		}
152
153
		//run appinfo/install.php
154
		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...
155
			self::includeAppScript($basedir . '/appinfo/install.php');
156
		}
157
158
		$appData = OC_App::getAppInfo($appId);
159
		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
160
161
		//set the installed version
162
		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false));
163
		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
164
165
		//set remote/public handlers
166
		foreach($info['remote'] as $name=>$path) {
167
			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
168
		}
169 View Code Duplication
		foreach($info['public'] as $name=>$path) {
170
			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
171
		}
172
173
		OC_App::setAppTypes($info['id']);
174
175
		return $info['id'];
176
	}
177
178
	/**
179
	 * @brief checks whether or not an app is installed
180
	 * @param string $app app
181
	 * @returns bool
182
	 *
183
	 * Checks whether or not an app is installed, i.e. registered in apps table.
184
	 */
185
	public static function isInstalled( $app ) {
186
		return (\OC::$server->getConfig()->getAppValue($app, "installed_version", null) !== null);
187
	}
188
189
	/**
190
	 * Updates the specified app from the appstore
191
	 *
192
	 * @param string $appId
193
	 * @return bool
194
	 */
195
	public function updateAppstoreApp($appId) {
196
		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...
197
			try {
198
				$this->downloadApp($appId);
199
			} catch (\Exception $e) {
200
				$this->logger->error($e->getMessage(), ['app' => 'core']);
201
				return false;
202
			}
203
			return OC_App::updateApp($appId);
204
		}
205
206
		return false;
207
	}
208
209
	/**
210
	 * Downloads an app and puts it into the app directory
211
	 *
212
	 * @param string $appId
213
	 *
214
	 * @throws \Exception If the installation was not successful
215
	 */
216
	public function downloadApp($appId) {
217
		$appId = strtolower($appId);
218
219
		$apps = $this->appFetcher->get();
220
		foreach($apps as $app) {
221
			if($app['id'] === $appId) {
222
				// Load the certificate
223
				$certificate = new X509();
224
				$certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
225
				$loadedCertificate = $certificate->loadX509($app['certificate']);
226
227
				// Verify if the certificate has been revoked
228
				$crl = new X509();
229
				$crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'));
230
				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
231
				if($crl->validateSignature() !== true) {
232
					throw new \Exception('Could not validate CRL signature');
233
				}
234
				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
235
				$revoked = $crl->getRevoked($csn);
236
				if ($revoked !== false) {
237
					throw new \Exception(
238
						sprintf(
239
							'Certificate "%s" has been revoked',
240
							$csn
241
						)
242
					);
243
				}
244
245
				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
246
				if($certificate->validateSignature() !== true) {
247
					throw new \Exception(
248
						sprintf(
249
							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
250
							$appId
251
						)
252
					);
253
				}
254
255
				// Verify if the certificate is issued for the requested app id
256
				$certInfo = openssl_x509_parse($app['certificate']);
257
				if(!isset($certInfo['subject']['CN'])) {
258
					throw new \Exception(
259
						sprintf(
260
							'App with id %s has a cert with no CN',
261
							$appId
262
						)
263
					);
264
				}
265
				if($certInfo['subject']['CN'] !== $appId) {
266
					throw new \Exception(
267
						sprintf(
268
							'App with id %s has a cert issued to %s',
269
							$appId,
270
							$certInfo['subject']['CN']
271
						)
272
					);
273
				}
274
275
				// Download the release
276
				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
277
				$client = $this->clientService->newClient();
278
				$client->get($app['releases'][0]['download'], ['save_to' => $tempFile]);
279
280
				// Check if the signature actually matches the downloaded content
281
				$certificate = openssl_get_publickey($app['certificate']);
282
				$verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512);
283
				openssl_free_key($certificate);
284
285
				if($verified === true) {
286
					// Seems to match, let's proceed
287
					$extractDir = $this->tempManager->getTemporaryFolder();
288
					$archive = new TAR($tempFile);
289
290
					if($archive) {
291
						$archive->extract($extractDir);
292
						$allFiles = scandir($extractDir);
293
						$folders = array_diff($allFiles, ['.', '..']);
294
						$folders = array_values($folders);
295
296
						if(count($folders) > 1) {
297
							throw new \Exception(
298
								sprintf(
299
									'Extracted app %s has more than 1 folder',
300
									$appId
301
								)
302
							);
303
						}
304
305
						// Check if appinfo/info.xml has the same app ID as well
306
						$loadEntities = libxml_disable_entity_loader(false);
307
						$xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml');
308
						libxml_disable_entity_loader($loadEntities);
309
						if((string)$xml->id !== $appId) {
310
							throw new \Exception(
311
								sprintf(
312
									'App for id %s has a wrong app ID in info.xml: %s',
313
									$appId,
314
									(string)$xml->id
315
								)
316
							);
317
						}
318
319
						// Check if the version is lower than before
320
						$currentVersion = OC_App::getAppVersion($appId);
321
						$newVersion = (string)$xml->version;
322
						if(version_compare($currentVersion, $newVersion) === 1) {
323
							throw new \Exception(
324
								sprintf(
325
									'App for id %s has version %s and tried to update to lower version %s',
326
									$appId,
327
									$currentVersion,
328
									$newVersion
329
								)
330
							);
331
						}
332
333
						$baseDir = OC_App::getInstallPath() . '/' . $appId;
334
						// Remove old app with the ID if existent
335
						OC_Helper::rmdirr($baseDir);
336
						// Move to app folder
337
						if(@mkdir($baseDir)) {
338
							$extractDir .= '/' . $folders[0];
339
							OC_Helper::copyr($extractDir, $baseDir);
340
						}
341
						OC_Helper::copyr($extractDir, $baseDir);
342
						OC_Helper::rmdirr($extractDir);
343
						return;
344
					} else {
345
						throw new \Exception(
346
							sprintf(
347
								'Could not extract app with ID %s to %s',
348
								$appId,
349
								$extractDir
350
							)
351
						);
352
					}
353
				} else {
354
					// Signature does not match
355
					throw new \Exception(
356
						sprintf(
357
							'App with id %s has invalid signature',
358
							$appId
359
						)
360
					);
361
				}
362
			}
363
		}
364
365
		throw new \Exception(
366
			sprintf(
367
				'Could not download app %s',
368
				$appId
369
			)
370
		);
371
	}
372
373
	/**
374
	 * Check if an update for the app is available
375
	 *
376
	 * @param string $appId
377
	 * @param AppFetcher $appFetcher
378
	 * @return string|false false or the version number of the update
379
	 */
380
	public static function isUpdateAvailable($appId,
381
									  AppFetcher $appFetcher) {
382
		static $isInstanceReadyForUpdates = null;
383
384
		if ($isInstanceReadyForUpdates === null) {
385
			$installPath = OC_App::getInstallPath();
386
			if ($installPath === false || $installPath === null) {
387
				$isInstanceReadyForUpdates = false;
388
			} else {
389
				$isInstanceReadyForUpdates = true;
390
			}
391
		}
392
393
		if ($isInstanceReadyForUpdates === false) {
394
			return false;
395
		}
396
397
		$apps = $appFetcher->get();
398
		foreach($apps as $app) {
399
			if($app['id'] === $appId) {
400
				$currentVersion = OC_App::getAppVersion($appId);
401
				$newestVersion = $app['releases'][0]['version'];
402
				if (version_compare($newestVersion, $currentVersion, '>')) {
403
					return $newestVersion;
404
				} else {
405
					return false;
406
				}
407
			}
408
		}
409
410
		return false;
411
	}
412
413
	/**
414
	 * Check if app is already downloaded
415
	 * @param string $name name of the application to remove
416
	 * @return boolean
417
	 *
418
	 * The function will check if the app is already downloaded in the apps repository
419
	 */
420
	public function isDownloaded($name) {
421
		foreach(\OC::$APPSROOTS as $dir) {
422
			$dirToTest  = $dir['path'];
423
			$dirToTest .= '/';
424
			$dirToTest .= $name;
425
			$dirToTest .= '/';
426
427
			if (is_dir($dirToTest)) {
428
				return true;
429
			}
430
		}
431
432
		return false;
433
	}
434
435
	/**
436
	 * Removes an app
437
	 * @param string $appId ID of the application to remove
438
	 * @return boolean
439
	 *
440
	 *
441
	 * This function works as follows
442
	 *   -# call uninstall repair steps
443
	 *   -# removing the files
444
	 *
445
	 * The function will not delete preferences, tables and the configuration,
446
	 * this has to be done by the function oc_app_uninstall().
447
	 */
448
	public function removeApp($appId) {
449
		if($this->isDownloaded( $appId )) {
450
			$appDir = OC_App::getInstallPath() . '/' . $appId;
451
			OC_Helper::rmdirr($appDir);
452
			return true;
453
		}else{
454
			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::writeLog() has been deprecated with message: 13.0.0 use log of \OCP\ILogger

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
455
456
			return false;
457
		}
458
459
	}
460
461
	/**
462
	 * Installs the app within the bundle and marks the bundle as installed
463
	 *
464
	 * @param Bundle $bundle
465
	 * @throws \Exception If app could not get installed
466
	 */
467
	public function installAppBundle(Bundle $bundle) {
468
		$appIds = $bundle->getAppIdentifiers();
469
		foreach($appIds as $appId) {
470
			if(!$this->isDownloaded($appId)) {
471
				$this->downloadApp($appId);
472
			}
473
			$this->installApp($appId);
474
			$app = new OC_App();
475
			$app->enable($appId);
476
		}
477
		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
478
		$bundles[] = $bundle->getIdentifier();
479
		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
480
	}
481
482
	/**
483
	 * Installs shipped apps
484
	 *
485
	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
486
	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
487
	 *                         working ownCloud at the end instead of an aborted update.
488
	 * @return array Array of error messages (appid => Exception)
489
	 */
490
	public static function installShippedApps($softErrors = false) {
491
		$errors = [];
492
		foreach(\OC::$APPSROOTS as $app_dir) {
493
			if($dir = opendir( $app_dir['path'] )) {
494
				while( false !== ( $filename = readdir( $dir ))) {
495
					if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
496
						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
497
							if(!Installer::isInstalled($filename)) {
498
								$info=OC_App::getAppInfo($filename);
499
								$enabled = isset($info['default_enable']);
500
								if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
501
									  && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
502
									if ($softErrors) {
503
										try {
504
											Installer::installShippedApp($filename);
505
										} catch (HintException $e) {
506
											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...
507
												$errors[$filename] = $e;
508
												continue;
509
											}
510
											throw $e;
511
										}
512
									} else {
513
										Installer::installShippedApp($filename);
514
									}
515
									\OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes');
516
								}
517
							}
518
						}
519
					}
520
				}
521
				closedir( $dir );
522
			}
523
		}
524
525
		return $errors;
526
	}
527
528
	/**
529
	 * install an app already placed in the app folder
530
	 * @param string $app id of the app to install
531
	 * @return integer
532
	 */
533
	public static function installShippedApp($app) {
534
		//install the database
535
		$appPath = OC_App::getAppPath($app);
536
		\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 535 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...
537
538
		if(is_file("$appPath/appinfo/database.xml")) {
539
			try {
540
				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
541
			} 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...
542
				throw new HintException(
543
					'Failed to enable app ' . $app,
544
					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer">support channels</a>.',
545
					0, $e
546
				);
547
			}
548
		} else {
549
			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
550
			$ms->migrate();
551
		}
552
553
		//run appinfo/install.php
554
		self::includeAppScript("$appPath/appinfo/install.php");
555
556
		$info = OC_App::getAppInfo($app);
557
		if (is_null($info)) {
558
			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...
559
		}
560
		\OC_App::setupBackgroundJobs($info['background-jobs']);
561
562
		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
563
564
		$config = \OC::$server->getConfig();
565
566
		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
567
		if (array_key_exists('ocsid', $info)) {
568
			$config->setAppValue($app, 'ocsid', $info['ocsid']);
569
		}
570
571
		//set remote/public handlers
572
		foreach($info['remote'] as $name=>$path) {
573
			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
574
		}
575
		foreach($info['public'] as $name=>$path) {
576
			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
577
		}
578
579
		OC_App::setAppTypes($info['id']);
580
581 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
582
			// requires that autoloading was registered for the app,
583
			// as happens before running the install.php some lines above
584
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
585
		}
586
587
		return $info['id'];
588
	}
589
590
	/**
591
	 * check the code of an app with some static code checks
592
	 * @param string $folder the folder of the app to check
593
	 * @return boolean true for app is o.k. and false for app is not o.k.
594
	 */
595
	public static function checkCode($folder) {
596
		// is the code checker enabled?
597
		if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
598
			return true;
599
		}
600
601
		$codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck()));
0 ignored issues
show
Bug introduced by
The call to CodeChecker::__construct() misses a required argument $checkMigrationSchema.

This check looks for function calls that miss required arguments.

Loading history...
602
		$errors = $codeChecker->analyseFolder(basename($folder), $folder);
603
604
		return empty($errors);
605
	}
606
607
	/**
608
	 * @param string $script
609
	 */
610
	private static function includeAppScript($script) {
611
		if ( file_exists($script) ){
612
			include $script;
613
		}
614
	}
615
}
616