Completed
Push — master ( e40705...fe8d45 )
by Thomas
08:24
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
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Brice Maron <[email protected]>
6
 * @author Christian Weiske <[email protected]>
7
 * @author Christopher Schäpers <[email protected]>
8
 * @author Frank Karlitschek <[email protected]>
9
 * @author Georg Ehrke <[email protected]>
10
 * @author Jakob Sack <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Jörn Friedrich Dreyer <[email protected]>
13
 * @author Kamil Domanski <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author michag86 <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author root <root@oc.(none)>
20
 * @author Thomas Müller <[email protected]>
21
 * @author Thomas Tanghus <[email protected]>
22
 *
23
 * @copyright Copyright (c) 2016, ownCloud GmbH.
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
40
namespace OC;
41
42
use OC\App\CodeChecker\CodeChecker;
43
use OC\App\CodeChecker\EmptyCheck;
44
use OC\App\CodeChecker\PrivateCheck;
45
use OC_App;
46
use OC_DB;
47
use OC_Helper;
48
49
/**
50
 * This class provides the functionality needed to install, update and remove plugins/apps
51
 */
52
class Installer {
53
54
	/**
55
	 *
56
	 * This function installs an app. All information needed are passed in the
57
	 * associative array $data.
58
	 * The following keys are required:
59
	 *   - source: string, can be "path" or "http"
60
	 *
61
	 * One of the following keys is required:
62
	 *   - path: path to the file containing the app
63
	 *   - href: link to the downloadable file containing the app
64
	 *
65
	 * The following keys are optional:
66
	 *   - pretend: boolean, if set true the system won't do anything
67
	 *   - noinstall: boolean, if true appinfo/install.php won't be loaded
68
	 *   - inactive: boolean, if set true the appconfig/app.sample.php won't be
69
	 *     renamed
70
	 *
71
	 * This function works as follows
72
	 *   -# fetching the file
73
	 *   -# unzipping it
74
	 *   -# check the code
75
	 *   -# installing the database at appinfo/database.xml
76
	 *   -# including appinfo/install.php
77
	 *   -# setting the installed version
78
	 *
79
	 * It is the task of oc_app_install to create the tables and do whatever is
80
	 * needed to get the app working.
81
	 *
82
	 * Installs an app
83
	 * @param array $data with all information
84
	 * @throws \Exception
85
	 * @return integer
86
	 */
87
	public static function installApp( $data = []) {
88
		$l = \OC::$server->getL10N('lib');
89
90
		list($extractDir, $path) = self::downloadApp($data);
91
92
		$info = self::checkAppsIntegrity($data, $extractDir, $path);
93
		$appId = OC_App::cleanAppId($info['id']);
94
		$basedir = OC_App::getInstallPath().'/'.$appId;
95
		//check if the destination directory already exists
96 View Code Duplication
		if(is_dir($basedir)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
97
			OC_Helper::rmdirr($extractDir);
98
			if($data['source']=='http') {
99
				unlink($path);
100
			}
101
			throw new \Exception($l->t("App directory already exists"));
102
		}
103
104
		if(!empty($data['pretent'])) {
105
			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::installApp 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...
106
		}
107
108
		//copy the app to the correct place
109
		if(@!mkdir($basedir)) {
110
			OC_Helper::rmdirr($extractDir);
111
			if($data['source']=='http') {
112
				unlink($path);
113
			}
114
			throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", [$basedir]));
115
		}
116
117
		$extractDir .= '/' . $info['id'];
118 View Code Duplication
		if(!file_exists($extractDir)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
119
			OC_Helper::rmdirr($basedir);
120
			throw new \Exception($l->t("Archive does not contain a directory named %s", $info['id']));
121
		}
122
		OC_Helper::copyr($extractDir, $basedir);
123
124
		//remove temporary files
125
		OC_Helper::rmdirr($extractDir);
126
127
		//install the database
128
		if (isset($appData['use-migrations']) && $appData['use-migrations'] === 'true') {
0 ignored issues
show
Bug introduced by
The variable $appData seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
129
			$ms = new \OC\DB\MigrationService($appId, \OC::$server->getDatabaseConnection());
130
			$ms->migrate();
131
		} else {
132
			if(is_file($basedir.'/appinfo/database.xml')) {
133
				if (\OC::$server->getAppConfig()->getValue($info['id'], 'installed_version') === null) {
134
					OC_DB::createDbFromStructure($basedir . '/appinfo/database.xml');
135
				} else {
136
					OC_DB::updateDbFromStructure($basedir . '/appinfo/database.xml');
137
				}
138
			}
139
		}
140
141
		\OC_App::setupBackgroundJobs($info['background-jobs']);
142
143
		//run appinfo/install.php
144
		if((!isset($data['noinstall']) or $data['noinstall']==false)) {
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']));
153
		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
154
155
		//set remote/public handlers
156 View Code Duplication
		foreach($info['remote'] as $name=>$path) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
157
			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
158
		}
159 View Code Duplication
		foreach($info['public'] as $name=>$path) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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
	 * @brief checks whether or not an app is installed
170
	 * @param string $app app
171
	 * @returns bool
172
	 *
173
	 * Checks whether or not an app is installed, i.e. registered in apps table.
174
	 */
175
	public static function 	isInstalled( $app ) {
176
		return (\OC::$server->getConfig()->getAppValue($app, "installed_version", null) !== null);
177
	}
178
179
	/**
180
	 * @brief Update an application
181
	 * @param array $info
182
	 * @param bool $isShipped
183
	 * @throws \Exception
184
	 * @return bool
185
	 *
186
	 * This function could work like described below, but currently it disables and then
187
	 * enables the app again. This does result in an updated app.
188
	 *
189
	 *
190
	 * This function installs an app. All information needed are passed in the
191
	 * associative array $info.
192
	 * The following keys are required:
193
	 *   - source: string, can be "path" or "http"
194
	 *
195
	 * One of the following keys is required:
196
	 *   - path: path to the file containing the app
197
	 *   - href: link to the downloadable file containing the app
198
	 *
199
	 * The following keys are optional:
200
	 *   - pretend: boolean, if set true the system won't do anything
201
	 *   - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded
202
	 *
203
	 * This function works as follows
204
	 *   -# fetching the file
205
	 *   -# removing the old files
206
	 *   -# unzipping new file
207
	 *   -# including appinfo/upgrade.php
208
	 *   -# setting the installed version
209
	 *
210
	 * upgrade.php can determine the current installed version of the app using
211
	 * "\OC::$server->getAppConfig()->getValue($appid, 'installed_version')"
212
	 */
213
	public static function updateApp($info= [], $isShipped=false) {
214
		list($extractDir, $path) = self::downloadApp($info);
215
		$info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped);
216
217
		$currentDir = OC_App::getAppPath($info['id']);
218
		$basedir  = OC_App::getInstallPath();
219
		$basedir .= '/';
220
		$basedir .= $info['id'];
221
222
		if($currentDir !== false && is_writable($currentDir)) {
223
			$basedir = $currentDir;
224
		}
225
		if(is_dir($basedir)) {
226
			OC_Helper::rmdirr($basedir);
227
		}
228
229
		$appInExtractDir = $extractDir;
230
		if (substr($extractDir, -1) !== '/') {
231
			$appInExtractDir .= '/';
232
		}
233
234
		$appInExtractDir .= $info['id'];
235
		OC_Helper::copyr($appInExtractDir, $basedir);
236
		OC_Helper::rmdirr($extractDir);
237
238
		return OC_App::updateApp($info['id']);
239
	}
240
241
	/**
242
	 * @param array $data
243
	 * @return array
244
	 * @throws \Exception
245
	 */
246
	public static function downloadApp($data = []) {
247
		$l = \OC::$server->getL10N('lib');
248
249
		if(!isset($data['source'])) {
250
			throw new \Exception($l->t("No source specified when installing app"));
251
		}
252
253
		//download the file if necessary
254
		if($data['source']=='http') {
255
			$pathInfo = pathinfo($data['href']);
256
			$extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
257
			$path = \OC::$server->getTempManager()->getTemporaryFile($extension);
258
			if(!isset($data['href'])) {
259
				throw new \Exception($l->t("No href specified when installing app from http"));
260
			}
261
			$client = \OC::$server->getHTTPClientService()->newClient();
262
			$client->get($data['href'], ['save_to' => $path]);
263
		} else {
264
			if(!isset($data['path'])) {
265
				throw new \Exception($l->t("No path specified when installing app from local file"));
266
			}
267
			$path=$data['path'];
268
		}
269
270
		//detect the archive type
271
		$mime = \OC::$server->getMimeTypeDetector()->detect($path);
272
		if ($mime !=='application/zip' && $mime !== 'application/x-gzip' && $mime !== 'application/x-bzip2') {
273
			throw new \Exception($l->t("Archives of type %s are not supported", [$mime]));
274
		}
275
276
		//extract the archive in a temporary folder
277
		$extractDir = \OC::$server->getTempManager()->getTemporaryFolder();
278
		OC_Helper::rmdirr($extractDir);
279
		mkdir($extractDir);
280
		if($archive=\OC\Archive\Archive::open($path)) {
281
			$archive->extract($extractDir);
282 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
283
			OC_Helper::rmdirr($extractDir);
284
			if($data['source']=='http') {
285
				unlink($path);
286
			}
287
			throw new \Exception($l->t("Failed to open archive when installing app"));
288
		}
289
290
		return [
291
			$extractDir,
292
			$path
293
		];
294
	}
295
296
	/**
297
	 * check an app's integrity
298
	 * @param array $data
299
	 * @param string $extractDir
300
	 * @param string $path
301
	 * @param bool $isShipped
302
	 * @return array
303
	 * @throws \Exception
304
	 */
305
	public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped = false) {
306
		$l = \OC::$server->getL10N('lib');
307
		//load the info.xml file of the app
308
		if(!is_file($extractDir.'/appinfo/info.xml')) {
309
			//try to find it in a subdir
310
			$dh=opendir($extractDir);
311
			if(is_resource($dh)) {
312
				while (($folder = readdir($dh)) !== false) {
313
					if($folder[0]!='.' and is_dir($extractDir.'/'.$folder)) {
314
						if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')) {
315
							$extractDir.='/'.$folder;
316
						}
317
					}
318
				}
319
			}
320
		}
321 View Code Duplication
		if(!is_file($extractDir.'/appinfo/info.xml')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
322
			OC_Helper::rmdirr($extractDir);
323
			if($data['source'] === 'http') {
324
				unlink($path);
325
			}
326
			throw new \Exception($l->t("App does not provide an info.xml file"));
327
		}
328
329
		$info = OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true);
330
		if(!is_array($info)) {
331
			throw new \Exception($l->t('App cannot be installed because appinfo file cannot be read.'));
332
		}
333
334
		// We can't trust the parsed info.xml file as it may have been tampered
335
		// with by an attacker and thus we need to use the local data to check
336
		// whether the application needs to be signed.
337
		$appId = OC_App::cleanAppId($data['appdata']['id']);
338
		$appBelongingToId = OC_App::getInternalAppIdByOcs($appId);
339 View Code Duplication
		if(is_string($appBelongingToId)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
340
			$previouslySigned = \OC::$server->getConfig()->getAppValue($appBelongingToId, 'signed', 'false');
341
		} else {
342
			$appBelongingToId = $info['id'];
343
			$previouslySigned = 'false';
344
		}
345
		if (file_exists($extractDir . '/appinfo/signature.json') || $previouslySigned === 'true') {
346
			\OC::$server->getConfig()->setAppValue($appBelongingToId, 'signed', 'true');
347
			$integrityResult = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature(
348
					$appBelongingToId,
349
					$extractDir
350
			);
351
			if($integrityResult !== []) {
352
				$e = new \Exception(
353
						$l->t(
354
								'Signature could not get checked. Please contact the app developer and check your admin screen.'
355
						)
356
				);
357
				throw $e;
358
			}
359
		}
360
361
		// check the code for not allowed calls
362
		if(!$isShipped && !Installer::checkCode($extractDir)) {
363
			OC_Helper::rmdirr($extractDir);
364
			throw new \Exception($l->t("App can't be installed because of not allowed code in the App"));
365
		}
366
367
		// check if the app is compatible with this version of ownCloud
368
		if(!OC_App::isAppCompatible(\OCP\Util::getVersion(), $info)) {
0 ignored issues
show
Documentation introduced by
\OCP\Util::getVersion() 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...
369
			OC_Helper::rmdirr($extractDir);
370
			throw new \Exception($l->t("App can't be installed because it is not compatible with this version of ownCloud"));
371
		}
372
373
		// check if shipped tag is set which is only allowed for apps that are shipped with ownCloud
374
		if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) {
375
			OC_Helper::rmdirr($extractDir);
376
			throw new \Exception($l->t("App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps"));
377
		}
378
379
		// check if the ocs version is the same as the version in info.xml/version
380
		$version = trim($info['version']);
381
382
		if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) {
383
			OC_Helper::rmdirr($extractDir);
384
			throw new \Exception($l->t("App can't be installed because the version in info.xml is not the same as the version reported from the app store"));
385
		}
386
387
		return $info;
388
	}
389
390
	/**
391
	 * Check if app is already downloaded
392
	 * @param string $name name of the application to remove
393
	 * @return boolean
394
	 *
395
	 * The function will check if the app is already downloaded in the apps repository
396
	 */
397
	public static function isDownloaded( $name ) {
398
		foreach(\OC::$APPSROOTS as $dir) {
399
			$dirToTest  = $dir['path'];
400
			$dirToTest .= '/';
401
			$dirToTest .= $name;
402
			$dirToTest .= '/';
403
404
			if (is_dir($dirToTest)) {
405
				return true;
406
			}
407
		}
408
409
		return false;
410
	}
411
412
	/**
413
	 * Removes an app
414
	 * @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...
415
	 * @return boolean
416
	 *
417
	 *
418
	 * This function works as follows
419
	 *   -# call uninstall repair steps
420
	 *   -# removing the files
421
	 *
422
	 * The function will not delete preferences, tables and the configuration,
423
	 * this has to be done by the function oc_app_uninstall().
424
	 */
425
	public static function removeApp($appId) {
426
427
		if(Installer::isDownloaded( $appId )) {
428
			$appDir=OC_App::getInstallPath() . '/' . $appId;
429
			OC_Helper::rmdirr($appDir);
430
431
			return true;
432
		}else{
433
			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
434
435
			return false;
436
		}
437
438
	}
439
440
	/**
441
	 * Installs shipped apps
442
	 *
443
	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
444
	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
445
	 *                         working ownCloud at the end instead of an aborted update.
446
	 * @return array Array of error messages (appid => Exception)
447
	 */
448
	public static function installShippedApps($softErrors = false) {
449
		$errors = [];
450
		foreach(\OC::$APPSROOTS as $app_dir) {
451
			if($dir = opendir( $app_dir['path'] )) {
452
				while( false !== ( $filename = readdir( $dir ))) {
453
					if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
454
						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
455
							if(!Installer::isInstalled($filename)) {
456
								$info=OC_App::getAppInfo($filename);
457
								$enabled = isset($info['default_enable']);
458
								if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
459
									  && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
460
									if ($softErrors) {
461
										try {
462
											Installer::installShippedApp($filename);
463
										} catch (\Doctrine\DBAL\Exception\TableExistsException $e) {
464
											$errors[$filename] = $e;
465
											continue;
466
										}
467
									} else {
468
										Installer::installShippedApp($filename);
469
									}
470
									\OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes');
471
								}
472
							}
473
						}
474
					}
475
				}
476
				closedir( $dir );
477
			}
478
		}
479
480
		return $errors;
481
	}
482
483
	/**
484
	 * install an app already placed in the app folder
485
	 * @param string $app id of the app to install
486
	 * @return integer
487
	 */
488
	public static function installShippedApp($app) {
489
490
		$info = OC_App::getAppInfo($app);
491
		if (is_null($info)) {
492
			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...
493
		}
494
495
		//install the database
496
		$appPath = OC_App::getAppPath($app);
497 View Code Duplication
		if (isset($info['use-migrations']) && $info['use-migrations'] === 'true') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
498
			$ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
499
			$ms->migrate();
500
		} else {
501
			if(is_file($appPath.'/appinfo/database.xml')) {
502
				OC_DB::createDbFromStructure($appPath . '/appinfo/database.xml');
503
			}
504
		}
505
506
		//run appinfo/install.php
507
		\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 496 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...
508
		self::includeAppScript("$appPath/appinfo/install.php");
509
510
		\OC_App::setupBackgroundJobs($info['background-jobs']);
511
512
		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
513
514
		$config = \OC::$server->getConfig();
515
516
		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
517
		if (array_key_exists('ocsid', $info)) {
518
			$config->setAppValue($app, 'ocsid', $info['ocsid']);
519
		}
520
521
		//set remote/public handlers
522
		foreach($info['remote'] as $name=>$path) {
523
			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
524
		}
525
		foreach($info['public'] as $name=>$path) {
526
			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
527
		}
528
529
		OC_App::setAppTypes($info['id']);
530
531
		return $info['id'];
532
	}
533
534
	/**
535
	 * check the code of an app with some static code checks
536
	 * @param string $folder the folder of the app to check
537
	 * @return boolean true for app is o.k. and false for app is not o.k.
538
	 */
539
	public static function checkCode($folder) {
540
		// is the code checker enabled?
541
		if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
542
			return true;
543
		}
544
545
		$codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck()));
546
		$errors = $codeChecker->analyseFolder($folder);
547
548
		return empty($errors);
549
	}
550
551
	/**
552
	 * @param $basedir
553
	 */
554
	private static function includeAppScript($script) {
555
		if ( file_exists($script) ){
556
			include $script;
557
		}
558
	}
559
}
560