Completed
Push — master ( 9db189...2d861c )
by Lukas
24s
created

Installer::installShippedApp()   C

Complexity

Conditions 8
Paths 34

Size

Total Lines 44
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 23
c 2
b 0
f 0
nc 34
nop 1
dl 0
loc 44
rs 5.3846
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Brice Maron <[email protected]>
8
 * @author Christian Weiske <[email protected]>
9
 * @author Christopher Schäpers <[email protected]>
10
 * @author Frank Karlitschek <[email protected]>
11
 * @author Georg Ehrke <[email protected]>
12
 * @author Jakob Sack <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Jörn Friedrich Dreyer <[email protected]>
15
 * @author Kamil Domanski <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author michag86 <[email protected]>
18
 * @author Morris Jobke <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Roeland Jago Douma <[email protected]>
21
 * @author root <root@oc.(none)>
22
 * @author Thomas Müller <[email protected]>
23
 * @author Thomas Tanghus <[email protected]>
24
 *
25
 * @license AGPL-3.0
26
 *
27
 * This code is free software: you can redistribute it and/or modify
28
 * it under the terms of the GNU Affero General Public License, version 3,
29
 * as published by the Free Software Foundation.
30
 *
31
 * This program is distributed in the hope that it will be useful,
32
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34
 * GNU Affero General Public License for more details.
35
 *
36
 * You should have received a copy of the GNU Affero General Public License, version 3,
37
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
38
 *
39
 */
40
41
namespace OC;
42
43
use OC\App\CodeChecker\CodeChecker;
44
use OC\App\CodeChecker\EmptyCheck;
45
use OC\App\CodeChecker\PrivateCheck;
46
use OC_App;
47
use OC_DB;
48
use OC_Helper;
49
50
/**
51
 * This class provides the functionality needed to install, update and remove plugins/apps
52
 */
53
class Installer {
54
55
	/**
56
	 *
57
	 * This function installs an app. All information needed are passed in the
58
	 * associative array $data.
59
	 * The following keys are required:
60
	 *   - source: string, can be "path" or "http"
61
	 *
62
	 * One of the following keys is required:
63
	 *   - path: path to the file containing the app
64
	 *   - href: link to the downloadable file containing the app
65
	 *
66
	 * The following keys are optional:
67
	 *   - pretend: boolean, if set true the system won't do anything
68
	 *   - noinstall: boolean, if true appinfo/install.php won't be loaded
69
	 *   - inactive: boolean, if set true the appconfig/app.sample.php won't be
70
	 *     renamed
71
	 *
72
	 * This function works as follows
73
	 *   -# fetching the file
74
	 *   -# unzipping it
75
	 *   -# check the code
76
	 *   -# installing the database at appinfo/database.xml
77
	 *   -# including appinfo/install.php
78
	 *   -# setting the installed version
79
	 *
80
	 * It is the task of oc_app_install to create the tables and do whatever is
81
	 * needed to get the app working.
82
	 *
83
	 * Installs an app
84
	 * @param array $data with all information
85
	 * @throws \Exception
86
	 * @return integer
87
	 */
88
	public static function installApp( $data = array()) {
89
		$l = \OC::$server->getL10N('lib');
90
91
		list($extractDir, $path) = self::downloadApp($data);
92
93
		$info = self::checkAppsIntegrity($data, $extractDir, $path);
94
		$appId = OC_App::cleanAppId($info['id']);
95
		$basedir = OC_App::getInstallPath().'/'.$appId;
96
		//check if the destination directory already exists
97
		if(is_dir($basedir)) {
98
			OC_Helper::rmdirr($extractDir);
99
			if($data['source']=='http') {
100
				unlink($path);
101
			}
102
			throw new \Exception($l->t("App directory already exists"));
103
		}
104
105
		if(!empty($data['pretent'])) {
106
			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...
107
		}
108
109
		//copy the app to the correct place
110
		if(@!mkdir($basedir)) {
111
			OC_Helper::rmdirr($extractDir);
112
			if($data['source']=='http') {
113
				unlink($path);
114
			}
115
			throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir)));
116
		}
117
118
		$extractDir .= '/' . $info['id'];
119 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...
120
			OC_Helper::rmdirr($basedir);
121
			throw new \Exception($l->t("Archive does not contain a directory named %s", $info['id']));
122
		}
123
		OC_Helper::copyr($extractDir, $basedir);
124
125
		//remove temporary files
126
		OC_Helper::rmdirr($extractDir);
127
128
		//install the database
129
		if(is_file($basedir.'/appinfo/database.xml')) {
130
			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...
131
				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
132
			} else {
133
				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
134
			}
135
		}
136
137
		\OC_App::setupBackgroundJobs($info['background-jobs']);
138
139
		//run appinfo/install.php
140
		if((!isset($data['noinstall']) or $data['noinstall']==false)) {
141
			self::includeAppScript($basedir . '/appinfo/install.php');
142
		}
143
144
		$appData = OC_App::getAppInfo($appId);
145
		OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']);
146
147
		//set the installed version
148
		\OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id']));
149
		\OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
150
151
		//set remote/public handlers
152
		foreach($info['remote'] as $name=>$path) {
153
			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
154
		}
155 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...
156
			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
157
		}
158
159
		OC_App::setAppTypes($info['id']);
160
161
		return $info['id'];
162
	}
163
164
	/**
165
	 * @brief checks whether or not an app is installed
166
	 * @param string $app app
167
	 * @returns bool
168
	 *
169
	 * Checks whether or not an app is installed, i.e. registered in apps table.
170
	 */
171
	public static function 	isInstalled( $app ) {
172
		return (\OC::$server->getConfig()->getAppValue($app, "installed_version", null) !== null);
173
	}
174
175
	/**
176
	 * @brief Update an application
177
	 * @param array $info
178
	 * @param bool $isShipped
179
	 * @throws \Exception
180
	 * @return bool
181
	 *
182
	 * This function could work like described below, but currently it disables and then
183
	 * enables the app again. This does result in an updated app.
184
	 *
185
	 *
186
	 * This function installs an app. All information needed are passed in the
187
	 * associative array $info.
188
	 * The following keys are required:
189
	 *   - source: string, can be "path" or "http"
190
	 *
191
	 * One of the following keys is required:
192
	 *   - path: path to the file containing the app
193
	 *   - href: link to the downloadable file containing the app
194
	 *
195
	 * The following keys are optional:
196
	 *   - pretend: boolean, if set true the system won't do anything
197
	 *   - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded
198
	 *
199
	 * This function works as follows
200
	 *   -# fetching the file
201
	 *   -# removing the old files
202
	 *   -# unzipping new file
203
	 *   -# including appinfo/upgrade.php
204
	 *   -# setting the installed version
205
	 *
206
	 * upgrade.php can determine the current installed version of the app using
207
	 * "\OC::$server->getAppConfig()->getValue($appid, 'installed_version')"
208
	 */
209
	public static function updateApp($info=array(), $isShipped=false) {
210
		list($extractDir, $path) = self::downloadApp($info);
211
		$info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped);
212
213
		$currentDir = OC_App::getAppPath($info['id']);
214
		$basedir  = OC_App::getInstallPath();
215
		$basedir .= '/';
216
		$basedir .= $info['id'];
217
218
		if($currentDir !== false && is_writable($currentDir)) {
219
			$basedir = $currentDir;
220
		}
221
		if(is_dir($basedir)) {
222
			OC_Helper::rmdirr($basedir);
223
		}
224
225
		$appInExtractDir = $extractDir;
226
		if (substr($extractDir, -1) !== '/') {
227
			$appInExtractDir .= '/';
228
		}
229
230
		$appInExtractDir .= $info['id'];
231
		OC_Helper::copyr($appInExtractDir, $basedir);
232
		OC_Helper::rmdirr($extractDir);
233
234
		return OC_App::updateApp($info['id']);
235
	}
236
237
	/**
238
	 * update an app by it's id
239
	 *
240
	 * @param integer $ocsId
241
	 * @return bool
242
	 * @throws \Exception
243
	 */
244
	public static function updateAppByOCSId($ocsId) {
245
		$ocsClient = new OCSClient(
246
			\OC::$server->getHTTPClientService(),
247
			\OC::$server->getConfig(),
248
			\OC::$server->getLogger()
249
		);
250
		$appData = $ocsClient->getApplication($ocsId, \OCP\Util::getVersion());
251
		$download = $ocsClient->getApplicationDownload($ocsId, \OCP\Util::getVersion());
252
253
		if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') {
254
			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
255
			$info = array(
256
				'source' => 'http',
257
				'href' => $download['downloadlink'],
258
				'appdata' => $appData
259
			);
260
		} else {
261
			throw new \Exception('Could not fetch app info!');
262
		}
263
264
		return self::updateApp($info);
265
	}
266
267
	/**
268
	 * @param array $data
269
	 * @return array
270
	 * @throws \Exception
271
	 */
272
	public static function downloadApp($data = array()) {
273
		$l = \OC::$server->getL10N('lib');
274
275
		if(!isset($data['source'])) {
276
			throw new \Exception($l->t("No source specified when installing app"));
277
		}
278
279
		//download the file if necessary
280
		if($data['source']=='http') {
281
			$pathInfo = pathinfo($data['href']);
282
			$extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
283
			$path = \OC::$server->getTempManager()->getTemporaryFile($extension);
284
			if(!isset($data['href'])) {
285
				throw new \Exception($l->t("No href specified when installing app from http"));
286
			}
287
			$client = \OC::$server->getHTTPClientService()->newClient();
288
			$client->get($data['href'], ['save_to' => $path]);
289
		} else {
290
			if(!isset($data['path'])) {
291
				throw new \Exception($l->t("No path specified when installing app from local file"));
292
			}
293
			$path=$data['path'];
294
		}
295
296
		//detect the archive type
297
		$mime = \OC::$server->getMimeTypeDetector()->detect($path);
298
		if ($mime !=='application/zip' && $mime !== 'application/x-gzip' && $mime !== 'application/x-bzip2') {
299
			throw new \Exception($l->t("Archives of type %s are not supported", array($mime)));
300
		}
301
302
		//extract the archive in a temporary folder
303
		$extractDir = \OC::$server->getTempManager()->getTemporaryFolder();
304
		OC_Helper::rmdirr($extractDir);
305
		mkdir($extractDir);
306
		if($archive=\OC\Archive\Archive::open($path)) {
307
			$archive->extract($extractDir);
308 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...
309
			OC_Helper::rmdirr($extractDir);
310
			if($data['source']=='http') {
311
				unlink($path);
312
			}
313
			throw new \Exception($l->t("Failed to open archive when installing app"));
314
		}
315
316
		return array(
317
			$extractDir,
318
			$path
319
		);
320
	}
321
322
	/**
323
	 * check an app's integrity
324
	 * @param array $data
325
	 * @param string $extractDir
326
	 * @param string $path
327
	 * @param bool $isShipped
328
	 * @return array
329
	 * @throws \Exception
330
	 */
331
	public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped = false) {
332
		$l = \OC::$server->getL10N('lib');
333
		//load the info.xml file of the app
334
		if(!is_file($extractDir.'/appinfo/info.xml')) {
335
			//try to find it in a subdir
336
			$dh=opendir($extractDir);
337
			if(is_resource($dh)) {
338
				while (($folder = readdir($dh)) !== false) {
339
					if($folder[0]!='.' and is_dir($extractDir.'/'.$folder)) {
340
						if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')) {
341
							$extractDir.='/'.$folder;
342
						}
343
					}
344
				}
345
			}
346
		}
347
		if(!is_file($extractDir.'/appinfo/info.xml')) {
348
			OC_Helper::rmdirr($extractDir);
349
			if($data['source'] === 'http') {
350
				unlink($path);
351
			}
352
			throw new \Exception($l->t("App does not provide an info.xml file"));
353
		}
354
355
		$info = OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true);
356
		if(!is_array($info)) {
357
			throw new \Exception($l->t('App cannot be installed because appinfo file cannot be read.'));
358
		}
359
360
		// We can't trust the parsed info.xml file as it may have been tampered
361
		// with by an attacker and thus we need to use the local data to check
362
		// whether the application needs to be signed.
363
		$appId = OC_App::cleanAppId($data['appdata']['id']);
364
		$appBelongingToId = OC_App::getInternalAppIdByOcs($appId);
365 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...
366
			$previouslySigned = \OC::$server->getConfig()->getAppValue($appBelongingToId, 'signed', 'false');
367
		} else {
368
			$appBelongingToId = $info['id'];
369
			$previouslySigned = 'false';
370
		}
371
		if($data['appdata']['level'] === OC_App::officialApp || $previouslySigned === 'true') {
372
			\OC::$server->getConfig()->setAppValue($appBelongingToId, 'signed', 'true');
373
			$integrityResult = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature(
374
					$appBelongingToId,
375
					$extractDir
376
			);
377
			if($integrityResult !== []) {
378
				$e = new \Exception(
379
						$l->t(
380
								'Signature could not get checked. Please contact the app developer and check your admin screen.'
381
						)
382
				);
383
				throw $e;
384
			}
385
		}
386
387
		// check the code for not allowed calls
388
		if(!$isShipped && !Installer::checkCode($extractDir)) {
389
			OC_Helper::rmdirr($extractDir);
390
			throw new \Exception($l->t("App can't be installed because of not allowed code in the App"));
391
		}
392
393
		// check if the app is compatible with this version of ownCloud
394
		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...
395
			OC_Helper::rmdirr($extractDir);
396
			throw new \Exception($l->t("App can't be installed because it is not compatible with this version of the server"));
397
		}
398
399
		// check if shipped tag is set which is only allowed for apps that are shipped with ownCloud
400
		if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) {
401
			OC_Helper::rmdirr($extractDir);
402
			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"));
403
		}
404
405
		// check if the ocs version is the same as the version in info.xml/version
406
		$version = trim($info['version']);
407
408
		if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) {
409
			OC_Helper::rmdirr($extractDir);
410
			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"));
411
		}
412
413
		return $info;
414
	}
415
416
	/**
417
	 * Check if an update for the app is available
418
	 * @param string $app
419
	 * @return string|false false or the version number of the update
420
	 *
421
	 * The function will check if an update for a version is available
422
	 */
423
	public static function isUpdateAvailable( $app ) {
424
		static $isInstanceReadyForUpdates = null;
425
426
		if ($isInstanceReadyForUpdates === null) {
427
			$installPath = OC_App::getInstallPath();
428
			if ($installPath === false || $installPath === null) {
429
				$isInstanceReadyForUpdates = false;
430
			} else {
431
				$isInstanceReadyForUpdates = true;
432
			}
433
		}
434
435
		if ($isInstanceReadyForUpdates === false) {
436
			return false;
437
		}
438
439
		$ocsid=\OC::$server->getAppConfig()->getValue( $app, 'ocsid', '');
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...
440
441
		if($ocsid<>'') {
442
			$ocsClient = new OCSClient(
443
				\OC::$server->getHTTPClientService(),
444
				\OC::$server->getConfig(),
445
				\OC::$server->getLogger()
446
			);
447
			$ocsdata = $ocsClient->getApplication($ocsid, \OCP\Util::getVersion());
448
			$ocsversion= (string) $ocsdata['version'];
449
			$currentversion=OC_App::getAppVersion($app);
450
			if (version_compare($ocsversion, $currentversion, '>')) {
451
				return($ocsversion);
452
			}else{
453
				return false;
454
			}
455
456
		}else{
457
			return false;
458
		}
459
460
	}
461
462
	/**
463
	 * Check if app is already downloaded
464
	 * @param string $name name of the application to remove
465
	 * @return boolean
466
	 *
467
	 * The function will check if the app is already downloaded in the apps repository
468
	 */
469
	public static function isDownloaded( $name ) {
470
		foreach(\OC::$APPSROOTS as $dir) {
471
			$dirToTest  = $dir['path'];
472
			$dirToTest .= '/';
473
			$dirToTest .= $name;
474
			$dirToTest .= '/';
475
476
			if (is_dir($dirToTest)) {
477
				return true;
478
			}
479
		}
480
481
		return false;
482
	}
483
484
	/**
485
	 * Removes an app
486
	 * @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...
487
	 * @return boolean
488
	 *
489
	 *
490
	 * This function works as follows
491
	 *   -# call uninstall repair steps
492
	 *   -# removing the files
493
	 *
494
	 * The function will not delete preferences, tables and the configuration,
495
	 * this has to be done by the function oc_app_uninstall().
496
	 */
497
	public static function removeApp($appId) {
498
499
		if(Installer::isDownloaded( $appId )) {
500
			$appDir=OC_App::getInstallPath() . '/' . $appId;
501
			OC_Helper::rmdirr($appDir);
502
503
			return true;
504
		}else{
505
			\OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR);
506
507
			return false;
508
		}
509
510
	}
511
512
	/**
513
	 * Installs shipped apps
514
	 *
515
	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
516
	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
517
	 *                         working ownCloud at the end instead of an aborted update.
518
	 * @return array Array of error messages (appid => Exception)
519
	 */
520
	public static function installShippedApps($softErrors = false) {
521
		$errors = [];
522
		foreach(\OC::$APPSROOTS as $app_dir) {
523
			if($dir = opendir( $app_dir['path'] )) {
524
				while( false !== ( $filename = readdir( $dir ))) {
525
					if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
526
						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
527
							if(!Installer::isInstalled($filename)) {
528
								$info=OC_App::getAppInfo($filename);
529
								$enabled = isset($info['default_enable']);
530
								if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
531
									  && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
532
									if ($softErrors) {
533
										try {
534
											Installer::installShippedApp($filename);
535
										} catch (\Doctrine\DBAL\Exception\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...
536
											$errors[$filename] = $e;
537
											continue;
538
										}
539
									} else {
540
										Installer::installShippedApp($filename);
541
									}
542
									\OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes');
543
								}
544
							}
545
						}
546
					}
547
				}
548
				closedir( $dir );
549
			}
550
		}
551
552
		return $errors;
553
	}
554
555
	/**
556
	 * install an app already placed in the app folder
557
	 * @param string $app id of the app to install
558
	 * @return integer
559
	 */
560
	public static function installShippedApp($app) {
561
		//install the database
562
		$appPath = OC_App::getAppPath($app);
563
		if(is_file("$appPath/appinfo/database.xml")) {
564
			OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
565
		}
566
567
		//run appinfo/install.php
568
		\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 562 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...
569
		self::includeAppScript("$appPath/appinfo/install.php");
570
571
		$info = OC_App::getAppInfo($app);
572
		if (is_null($info)) {
573
			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...
574
		}
575
		\OC_App::setupBackgroundJobs($info['background-jobs']);
576
577
		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
578
579
		$config = \OC::$server->getConfig();
580
581
		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
582
		if (array_key_exists('ocsid', $info)) {
583
			$config->setAppValue($app, 'ocsid', $info['ocsid']);
584
		}
585
586
		//set remote/public handlers
587
		foreach($info['remote'] as $name=>$path) {
588
			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
589
		}
590
		foreach($info['public'] as $name=>$path) {
591
			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
592
		}
593
594
		OC_App::setAppTypes($info['id']);
595
596
		if(isset($info['settings']) && is_array($info['settings'])) {
597
			// requires that autoloading was registered for the app,
598
			// as happens before running the install.php some lines above
599
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
600
		}
601
602
		return $info['id'];
603
	}
604
605
	/**
606
	 * check the code of an app with some static code checks
607
	 * @param string $folder the folder of the app to check
608
	 * @return boolean true for app is o.k. and false for app is not o.k.
609
	 */
610
	public static function checkCode($folder) {
611
		// is the code checker enabled?
612
		if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
613
			return true;
614
		}
615
616
		$codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck()));
617
		$errors = $codeChecker->analyseFolder($folder);
618
619
		return empty($errors);
620
	}
621
622
	/**
623
	 * @param $basedir
624
	 */
625
	private static function includeAppScript($script) {
626
		if ( file_exists($script) ){
627
			include $script;
628
		}
629
	}
630
}
631