Completed
Pull Request — stable9 (#2060)
by Joas
21:38 queued 14:09
created

OC_Installer::installShippedApp()   C

Complexity

Conditions 7
Paths 19

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 25
nc 19
nop 1
dl 0
loc 43
rs 6.7272
c 0
b 0
f 0
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 Robin McCorkell <[email protected]>
21
 * @author root <root@oc.(none)>
22
 * @author Thomas Müller <[email protected]>
23
 * @author Thomas Tanghus <[email protected]>
24
 * @author Vincent Petry <[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
use OC\App\CodeChecker\CodeChecker;
43
use OC\App\CodeChecker\EmptyCheck;
44
use OC\App\CodeChecker\PrivateCheck;
45
use OC\OCSClient;
46
47
/**
48
 * This class provides the functionality needed to install, update and remove plugins/apps
49
 */
50
class OC_Installer{
51
52
	/**
53
	 *
54
	 * This function installs an app. All information needed are passed in the
55
	 * associative array $data.
56
	 * The following keys are required:
57
	 *   - source: string, can be "path" or "http"
58
	 *
59
	 * One of the following keys is required:
60
	 *   - path: path to the file containing the app
61
	 *   - href: link to the downloadable file containing the app
62
	 *
63
	 * The following keys are optional:
64
	 *   - pretend: boolean, if set true the system won't do anything
65
	 *   - noinstall: boolean, if true appinfo/install.php won't be loaded
66
	 *   - inactive: boolean, if set true the appconfig/app.sample.php won't be
67
	 *     renamed
68
	 *
69
	 * This function works as follows
70
	 *   -# fetching the file
71
	 *   -# unzipping it
72
	 *   -# check the code
73
	 *   -# installing the database at appinfo/database.xml
74
	 *   -# including appinfo/install.php
75
	 *   -# setting the installed version
76
	 *
77
	 * It is the task of oc_app_install to create the tables and do whatever is
78
	 * needed to get the app working.
79
	 *
80
	 * Installs an app
81
	 * @param array $data with all information
82
	 * @throws \Exception
83
	 * @return integer
84
	 */
85
	public static function installApp( $data = array()) {
86
		$l = \OC::$server->getL10N('lib');
87
88
		list($extractDir, $path) = self::downloadApp($data);
89
90
		$info = self::checkAppsIntegrity($data, $extractDir, $path);
91
		$appId = OC_App::cleanAppId($info['id']);
92
		$basedir = OC_App::getInstallPath().'/'.$appId;
93
		//check if the destination directory already exists
94
		if(is_dir($basedir)) {
95
			OC_Helper::rmdirr($extractDir);
96
			if($data['source']=='http') {
97
				unlink($path);
98
			}
99
			throw new \Exception($l->t("App directory already exists"));
100
		}
101
102
		if(!empty($data['pretent'])) {
103
			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...
104
		}
105
106
		//copy the app to the correct place
107
		if(@!mkdir($basedir)) {
108
			OC_Helper::rmdirr($extractDir);
109
			if($data['source']=='http') {
110
				unlink($path);
111
			}
112
			throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir)));
113
		}
114
115
		$extractDir .= '/' . $info['id'];
116 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...
117
			OC_Helper::rmdirr($basedir);
118
			throw new \Exception($l->t("Archive does not contain a directory named %s", $info['id']));
119
		}
120
		OC_Helper::copyr($extractDir, $basedir);
121
122
		//remove temporary files
123
		OC_Helper::rmdirr($extractDir);
124
125
		//install the database
126
		if(is_file($basedir.'/appinfo/database.xml')) {
127
			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...
128
				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
129
			} else {
130
				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
131
			}
132
		}
133
134
		//run appinfo/install.php
135
		if((!isset($data['noinstall']) or $data['noinstall']==false)) {
136
			self::includeAppScript($basedir . '/appinfo/install.php');
137
		}
138
139
		//set the installed version
140
		\OC::$server->getAppConfig()->setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id']));
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

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...
141
		\OC::$server->getAppConfig()->setValue($info['id'], 'enabled', 'no');
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

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...
142
143
		//set remote/public handelers
144
		foreach($info['remote'] as $name=>$path) {
145
			OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Config::setAppValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

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...
146
		}
147
		foreach($info['public'] as $name=>$path) {
148
			OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Config::setAppValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

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...
149
		}
150
151
		OC_App::setAppTypes($info['id']);
152
153
		return $info['id'];
154
	}
155
156
	/**
157
	 * @brief checks whether or not an app is installed
158
	 * @param string $app app
159
	 * @returns bool
160
	 *
161
	 * Checks whether or not an app is installed, i.e. registered in apps table.
162
	 */
163
	public static function isInstalled( $app ) {
164
		return (\OC::$server->getAppConfig()->getValue($app, "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...
165
	}
166
167
	/**
168
	 * @brief Update an application
169
	 * @param array $info
170
	 * @param bool $isShipped
171
	 * @throws Exception
172
	 * @return bool
173
	 *
174
	 * This function could work like described below, but currently it disables and then
175
	 * enables the app again. This does result in an updated app.
176
	 *
177
	 *
178
	 * This function installs an app. All information needed are passed in the
179
	 * associative array $info.
180
	 * The following keys are required:
181
	 *   - source: string, can be "path" or "http"
182
	 *
183
	 * One of the following keys is required:
184
	 *   - path: path to the file containing the app
185
	 *   - href: link to the downloadable file containing the app
186
	 *
187
	 * The following keys are optional:
188
	 *   - pretend: boolean, if set true the system won't do anything
189
	 *   - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded
190
	 *
191
	 * This function works as follows
192
	 *   -# fetching the file
193
	 *   -# removing the old files
194
	 *   -# unzipping new file
195
	 *   -# including appinfo/upgrade.php
196
	 *   -# setting the installed version
197
	 *
198
	 * upgrade.php can determine the current installed version of the app using
199
	 * "\OC::$server->getAppConfig()->getValue($appid, 'installed_version')"
200
	 */
201
	public static function updateApp($info=array(), $isShipped=false) {
202
		list($extractDir, $path) = self::downloadApp($info);
203
		$info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped);
204
205
		$currentDir = OC_App::getAppPath($info['id']);
206
		$basedir  = OC_App::getInstallPath();
207
		$basedir .= '/';
208
		$basedir .= $info['id'];
209
210
		if($currentDir !== false && is_writable($currentDir)) {
211
			$basedir = $currentDir;
212
		}
213
		if(is_dir($basedir)) {
214
			OC_Helper::rmdirr($basedir);
215
		}
216
217
		$appInExtractDir = $extractDir;
218
		if (substr($extractDir, -1) !== '/') {
219
			$appInExtractDir .= '/';
220
		}
221
222
		$appInExtractDir .= $info['id'];
223
		OC_Helper::copyr($appInExtractDir, $basedir);
224
		OC_Helper::rmdirr($extractDir);
225
226
		return OC_App::updateApp($info['id']);
227
	}
228
229
	/**
230
	 * update an app by it's id
231
	 *
232
	 * @param integer $ocsId
233
	 * @return bool
234
	 * @throws Exception
235
	 */
236
	public static function updateAppByOCSId($ocsId) {
237
		$ocsClient = new OCSClient(
238
			\OC::$server->getHTTPClientService(),
239
			\OC::$server->getConfig(),
240
			\OC::$server->getLogger()
241
		);
242
		$appData = $ocsClient->getApplication($ocsId, \OCP\Util::getVersion());
243
		$download = $ocsClient->getApplicationDownload($ocsId, \OCP\Util::getVersion());
244
245
		if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') {
246
			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
247
			$info = array(
248
				'source' => 'http',
249
				'href' => $download['downloadlink'],
250
				'appdata' => $appData
251
			);
252
		} else {
253
			throw new \Exception('Could not fetch app info!');
254
		}
255
256
		return self::updateApp($info);
257
	}
258
259
	/**
260
	 * @param array $data
261
	 * @return array
262
	 * @throws Exception
263
	 */
264
	public static function downloadApp($data = array()) {
265
		$l = \OC::$server->getL10N('lib');
266
267
		if(!isset($data['source'])) {
268
			throw new \Exception($l->t("No source specified when installing app"));
269
		}
270
271
		//download the file if necessary
272
		if($data['source']=='http') {
273
			$pathInfo = pathinfo($data['href']);
274
			$extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
275
			$path = \OC::$server->getTempManager()->getTemporaryFile($extension);
276
			if(!isset($data['href'])) {
277
				throw new \Exception($l->t("No href specified when installing app from http"));
278
			}
279
			$client = \OC::$server->getHTTPClientService()->newClient();
280
			$client->get($data['href'], ['save_to' => $path]);
281
		} else {
282
			if(!isset($data['path'])) {
283
				throw new \Exception($l->t("No path specified when installing app from local file"));
284
			}
285
			$path=$data['path'];
286
		}
287
288
		//detect the archive type
289
		$mime = \OC::$server->getMimeTypeDetector()->detect($path);
290
		if ($mime !=='application/zip' && $mime !== 'application/x-gzip' && $mime !== 'application/x-bzip2') {
291
			throw new \Exception($l->t("Archives of type %s are not supported", array($mime)));
292
		}
293
294
		//extract the archive in a temporary folder
295
		$extractDir = \OC::$server->getTempManager()->getTemporaryFolder();
296
		OC_Helper::rmdirr($extractDir);
297
		mkdir($extractDir);
298
		if($archive=OC_Archive::open($path)) {
299
			$archive->extract($extractDir);
300 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...
301
			OC_Helper::rmdirr($extractDir);
302
			if($data['source']=='http') {
303
				unlink($path);
304
			}
305
			throw new \Exception($l->t("Failed to open archive when installing app"));
306
		}
307
308
		return array(
309
			$extractDir,
310
			$path
311
		);
312
	}
313
314
	/**
315
	 * check an app's integrity
316
	 * @param array $data
317
	 * @param string $extractDir
318
	 * @param string $path
319
	 * @param bool $isShipped
320
	 * @return array
321
	 * @throws \Exception
322
	 */
323
	public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped = false) {
324
		$l = \OC::$server->getL10N('lib');
325
		//load the info.xml file of the app
326
		if(!is_file($extractDir.'/appinfo/info.xml')) {
327
			//try to find it in a subdir
328
			$dh=opendir($extractDir);
329
			if(is_resource($dh)) {
330
				while (($folder = readdir($dh)) !== false) {
331
					if($folder[0]!='.' and is_dir($extractDir.'/'.$folder)) {
332
						if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')) {
333
							$extractDir.='/'.$folder;
334
						}
335
					}
336
				}
337
			}
338
		}
339
		if(!is_file($extractDir.'/appinfo/info.xml')) {
340
			OC_Helper::rmdirr($extractDir);
341
			if($data['source'] === 'http') {
342
				unlink($path);
343
			}
344
			throw new \Exception($l->t("App does not provide an info.xml file"));
345
		}
346
347
		$info = OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true);
348
		if(!is_array($info)) {
349
			throw new \Exception($l->t('App cannot be installed because appinfo file cannot be read.'));
350
		}
351
352
		// We can't trust the parsed info.xml file as it may have been tampered
353
		// with by an attacker and thus we need to use the local data to check
354
		// whether the application needs to be signed.
355
		$appId = OC_App::cleanAppId($data['appdata']['id']);
356
		$appBelongingToId = OC_App::getInternalAppIdByOcs($appId);
357 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...
358
			$previouslySigned = \OC::$server->getConfig()->getAppValue($appBelongingToId, 'signed', 'false');
359
		} else {
360
			$appBelongingToId = $info['id'];
361
			$previouslySigned = 'false';
362
		}
363
		if($data['appdata']['level'] === OC_App::officialApp || $previouslySigned === 'true') {
364
			\OC::$server->getConfig()->setAppValue($appBelongingToId, 'signed', 'true');
365
			$integrityResult = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature(
366
					$appBelongingToId,
367
					$extractDir
368
			);
369
			if($integrityResult !== []) {
370
				$e = new \Exception(
371
						$l->t(
372
								'Signature could not get checked. Please contact the app developer and check your admin screen.'
373
						)
374
				);
375
				throw $e;
376
			}
377
		}
378
379
		// check the code for not allowed calls
380
		if(!$isShipped && !OC_Installer::checkCode($extractDir)) {
381
			OC_Helper::rmdirr($extractDir);
382
			throw new \Exception($l->t("App can't be installed because of not allowed code in the App"));
383
		}
384
385
		// check if the app is compatible with this version of ownCloud
386
		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...
387
			OC_Helper::rmdirr($extractDir);
388
			throw new \Exception($l->t("App can't be installed because it is not compatible with this version of Nextcloud"));
389
		}
390
391
		// check if shipped tag is set which is only allowed for apps that are shipped with ownCloud
392
		if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) {
393
			OC_Helper::rmdirr($extractDir);
394
			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"));
395
		}
396
397
		// check if the ocs version is the same as the version in info.xml/version
398
		$versionFile= $extractDir.'/appinfo/version';
399
		if(is_file($versionFile)) {
400
			$version = trim(file_get_contents($versionFile));
401
		}else{
402
			$version = trim($info['version']);
403
		}
404
405
		if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) {
406
			OC_Helper::rmdirr($extractDir);
407
			throw new \Exception($l->t("App can't be installed because the version in info.xml/version is not the same as the version reported from the app store"));
408
		}
409
410
		return $info;
411
	}
412
413
	/**
414
	 * Check if an update for the app is available
415
	 * @param string $app
416
	 * @return string|false false or the version number of the update
417
	 *
418
	 * The function will check if an update for a version is available
419
	 */
420
	public static function isUpdateAvailable( $app ) {
421
		static $isInstanceReadyForUpdates = null;
422
423
		if ($isInstanceReadyForUpdates === null) {
424
			$installPath = OC_App::getInstallPath();
425
			if ($installPath === false || $installPath === null) {
426
				$isInstanceReadyForUpdates = false;
427
			} else {
428
				$isInstanceReadyForUpdates = true;
429
			}
430
		}
431
432
		if ($isInstanceReadyForUpdates === false) {
433
			return false;
434
		}
435
436
		$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...
437
438
		if($ocsid<>'') {
439
			$ocsClient = new OCSClient(
440
				\OC::$server->getHTTPClientService(),
441
				\OC::$server->getConfig(),
442
				\OC::$server->getLogger()
443
			);
444
			$ocsdata = $ocsClient->getApplication($ocsid, \OCP\Util::getVersion());
445
			$ocsversion= (string) $ocsdata['version'];
446
			$currentversion=OC_App::getAppVersion($app);
447
			if (version_compare($ocsversion, $currentversion, '>')) {
448
				return($ocsversion);
449
			}else{
450
				return false;
451
			}
452
453
		}else{
454
			return false;
455
		}
456
457
	}
458
459
	/**
460
	 * Check if app is already downloaded
461
	 * @param string $name name of the application to remove
462
	 * @return boolean
463
	 *
464
	 * The function will check if the app is already downloaded in the apps repository
465
	 */
466
	public static function isDownloaded( $name ) {
467
		foreach(OC::$APPSROOTS as $dir) {
468
			$dirToTest  = $dir['path'];
469
			$dirToTest .= '/';
470
			$dirToTest .= $name;
471
			$dirToTest .= '/';
472
473
			if (is_dir($dirToTest)) {
474
				return true;
475
			}
476
		}
477
478
		return false;
479
	}
480
481
	/**
482
	 * Removes an app
483
	 * @param string $name name of the application to remove
484
	 * @param array $options options
485
	 * @return boolean
486
	 *
487
	 * This function removes an app. $options is an associative array. The
488
	 * following keys are optional:ja
489
	 *   - keeppreferences: boolean, if true the user preferences won't be deleted
490
	 *   - keepappconfig: boolean, if true the config will be kept
491
	 *   - keeptables: boolean, if true the database will be kept
492
	 *   - keepfiles: boolean, if true the user files will be kept
493
	 *
494
	 * This function works as follows
495
	 *   -# including appinfo/remove.php
496
	 *   -# removing the files
497
	 *
498
	 * The function will not delete preferences, tables and the configuration,
499
	 * this has to be done by the function oc_app_uninstall().
500
	 */
501
	public static function removeApp( $name, $options = array()) {
502
503
		if(isset($options['keeppreferences']) and $options['keeppreferences']==false ) {
504
			// todo
505
			// remove preferences
506
		}
507
508
		if(isset($options['keepappconfig']) and $options['keepappconfig']==false ) {
509
			// todo
510
			// remove app config
511
		}
512
513
		if(isset($options['keeptables']) and $options['keeptables']==false ) {
514
			// todo
515
			// remove app database tables
516
		}
517
518
		if(isset($options['keepfiles']) and $options['keepfiles']==false ) {
519
			// todo
520
			// remove user files
521
		}
522
523
		if(OC_Installer::isDownloaded( $name )) {
524
			$appdir=OC_App::getInstallPath().'/'.$name;
525
			OC_Helper::rmdirr($appdir);
526
527
			return true;
528
		}else{
529
			\OCP\Util::writeLog('core', 'can\'t remove app '.$name.'. It is not installed.', \OCP\Util::ERROR);
530
531
			return false;
532
		}
533
534
	}
535
536
	/**
537
	 * Installs shipped apps
538
	 *
539
	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
540
	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
541
	 *                         working ownCloud at the end instead of an aborted update.
542
	 * @return array Array of error messages (appid => Exception)
543
	 */
544
	public static function installShippedApps($softErrors = false) {
545
		$errors = [];
546
		foreach(OC::$APPSROOTS as $app_dir) {
547
			if($dir = opendir( $app_dir['path'] )) {
548
				while( false !== ( $filename = readdir( $dir ))) {
549
					if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
550
						if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) {
551
							if(!OC_Installer::isInstalled($filename)) {
552
								$info=OC_App::getAppInfo($filename);
553
								$enabled = isset($info['default_enable']);
554
								if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps()))
555
									  && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') {
556
									if ($softErrors) {
557
										try {
558
											OC_Installer::installShippedApp($filename);
559
										} catch (\OC\HintException $e) {
560
											if ($e->getPrevious() instanceof \Doctrine\DBAL\Exception\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...
561
												$errors[$filename] = $e;
562
												continue;
563
											}
564
											throw $e;
565
										}
566
									} else {
567
										OC_Installer::installShippedApp($filename);
568
									}
569
									\OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes');
570
								}
571
							}
572
						}
573
					}
574
				}
575
				closedir( $dir );
576
			}
577
		}
578
579
		return $errors;
580
	}
581
582
	/**
583
	 * install an app already placed in the app folder
584
	 * @param string $app id of the app to install
585
	 * @return integer
586
	 */
587
	public static function installShippedApp($app) {
588
		//install the database
589
		$appPath = OC_App::getAppPath($app);
590
		if(is_file("$appPath/appinfo/database.xml")) {
591
			try {
592
				OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
593
			} 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...
594
				throw new \OC\HintException(
595
					'Failed to enable app ' . $app,
596
					'Please ask for help via one of our <a href="https://nextcloud.com/support/" target="_blank" rel="noreferrer">support channels</a>.',
597
					0, $e
598
				);
599
			}
600
		}
601
602
		//run appinfo/install.php
603
		\OC::$loader->addValidRoot($appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by \OC_App::getAppPath($app) on line 589 can also be of type false; however, OC\Autoloader::addValidRoot() 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...
604
		self::includeAppScript("$appPath/appinfo/install.php");
605
606
		$info = OC_App::getAppInfo($app);
607
		if (is_null($info)) {
608
			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...
609
		}
610
611
		$config = \OC::$server->getConfig();
612
613
		$config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app));
614
		if (array_key_exists('ocsid', $info)) {
615
			$config->setAppValue($app, 'ocsid', $info['ocsid']);
616
		}
617
618
		//set remote/public handlers
619
		foreach($info['remote'] as $name=>$path) {
620
			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
621
		}
622
		foreach($info['public'] as $name=>$path) {
623
			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
624
		}
625
626
		OC_App::setAppTypes($info['id']);
627
628
		return $info['id'];
629
	}
630
631
	/**
632
	 * check the code of an app with some static code checks
633
	 * @param string $folder the folder of the app to check
634
	 * @return boolean true for app is o.k. and false for app is not o.k.
635
	 */
636
	public static function checkCode($folder) {
637
		// is the code checker enabled?
638
		if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) {
639
			return true;
640
		}
641
642
		$codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck()));
643
		$errors = $codeChecker->analyseFolder($folder);
644
645
		return empty($errors);
646
	}
647
648
	/**
649
	 * @param $basedir
650
	 */
651
	private static function includeAppScript($script) {
652
		if ( file_exists($script) ){
653
			include $script;
654
		}
655
	}
656
}
657