Completed
Push — master ( 5a81b7...a6ad21 )
by Thomas
25:30
created

OC_App   F

Complexity

Total Complexity 212

Size/Duplication

Total Lines 1326
Duplicated Lines 1.36 %

Coupling/Cohesion

Components 3
Dependencies 32

Importance

Changes 0
Metric Value
dl 18
loc 1326
rs 0.5217
c 0
b 0
f 0
wmc 212
lcom 3
cbo 32

50 Methods

Rating   Name   Duplication   Size   Complexity  
A cleanAppId() 0 3 1
A isAppLoaded() 0 3 1
C loadApps() 0 26 8
B loadApp() 0 28 6
A registerAutoloading() 0 11 2
A requireAppFile() 0 12 3
A isType() 0 12 4
A getAppTypes() 0 12 3
A setAppTypes() 0 14 3
A isShipped() 0 3 1
B getEnabledApps() 0 25 4
A isEnabled() 0 3 1
B enable() 0 28 5
A downloadApp() 0 16 3
A removeApp() 0 7 2
A disable() 0 22 3
B getSettingsNavigation() 0 64 7
A proceedNavigation() 0 15 3
B getInstallPath() 0 14 5
D findAppInDirectories() 0 40 10
A getAppPath() 0 10 4
A isAppDirWritable() 0 4 2
A getAppWebPath() 0 6 2
A getAppVersion() 0 7 3
A getAppVersionByPath() 0 5 2
C getAppInfo() 0 31 8
A getNavigation() 0 5 1
A getCurrentApp() 0 17 4
A getForms() 0 21 4
A registerAdmin() 0 6 1
A registerPersonal() 0 6 1
A registerLogIn() 0 3 1
A getAlternativeLogIns() 0 3 1
C getAllApps() 0 24 8
F listAllApps() 0 106 24
A getInternalAppIdByOcs() 0 9 3
B getAppstoreApps() 0 55 8
A shouldUpgrade() 0 11 4
A adjustVersionParts() 0 13 3
C isAppCompatible() 12 36 11
A getAppVersions() 0 9 2
C installApp() 0 74 12
C updateApp() 6 40 8
B executeRepairSteps() 0 22 4
A setupBackgroundJobs() 0 6 2
A setupLiveMigrations() 0 8 2
A getStorage() 0 17 4
B parseAppInfo() 0 30 4
A checkAppDependencies() 0 12 2
A enableThemeIfApplicable() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like OC_App often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OC_App, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Bernhard Posselt <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Borjan Tchakaloff <[email protected]>
8
 * @author Brice Maron <[email protected]>
9
 * @author Christopher Schäpers <[email protected]>
10
 * @author Felix Moeller <[email protected]>
11
 * @author Frank Karlitschek <[email protected]>
12
 * @author Georg Ehrke <[email protected]>
13
 * @author Jakob Sack <[email protected]>
14
 * @author Jan-Christoph Borchardt <[email protected]>
15
 * @author Joas Schilling <[email protected]>
16
 * @author Jörn Friedrich Dreyer <[email protected]>
17
 * @author Kamil Domanski <[email protected]>
18
 * @author Klaas Freitag <[email protected]>
19
 * @author Lukas Reschke <[email protected]>
20
 * @author Markus Goetz <[email protected]>
21
 * @author Morris Jobke <[email protected]>
22
 * @author RealRancor <[email protected]>
23
 * @author Robin Appelman <[email protected]>
24
 * @author Robin McCorkell <[email protected]>
25
 * @author Roeland Jago Douma <[email protected]>
26
 * @author Sam Tuke <[email protected]>
27
 * @author Thomas Müller <[email protected]>
28
 * @author Thomas Tanghus <[email protected]>
29
 * @author Tom Needham <[email protected]>
30
 * @author Vincent Petry <[email protected]>
31
 *
32
 * @copyright Copyright (c) 2016, ownCloud GmbH.
33
 * @license AGPL-3.0
34
 *
35
 * This code is free software: you can redistribute it and/or modify
36
 * it under the terms of the GNU Affero General Public License, version 3,
37
 * as published by the Free Software Foundation.
38
 *
39
 * This program is distributed in the hope that it will be useful,
40
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
41
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42
 * GNU Affero General Public License for more details.
43
 *
44
 * You should have received a copy of the GNU Affero General Public License, version 3,
45
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
46
 *
47
 */
48
use OC\App\DependencyAnalyzer;
49
use OC\App\InfoParser;
50
use OC\App\Platform;
51
use OC\Installer;
52
use OC\OCSClient;
53
use OC\Repair;
54
55
/**
56
 * This class manages the apps. It allows them to register and integrate in the
57
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
58
 * upgrading and removing apps.
59
 */
60
class OC_App {
61
	static private $appVersion = [];
62
	static private $adminForms = [];
63
	static private $personalForms = [];
64
	static private $appInfo = [];
65
	static private $appTypes = [];
66
	static private $loadedApps = [];
67
	static private $altLogin = [];
68
	const officialApp = 200;
69
70
	/**
71
	 * clean the appId
72
	 *
73
	 * @param string|boolean $app AppId that needs to be cleaned
74
	 * @return string
75
	 */
76
	public static function cleanAppId($app) {
77
		return str_replace(['\0', '/', '\\', '..'], '', $app);
78
	}
79
80
	/**
81
	 * Check if an app is loaded
82
	 *
83
	 * @param string $app
84
	 * @return bool
85
	 */
86
	public static function isAppLoaded($app) {
87
		return in_array($app, self::$loadedApps, true);
88
	}
89
90
	/**
91
	 * loads all apps
92
	 *
93
	 * @param string[] | string | null $types
94
	 * @return bool
95
	 *
96
	 * This function walks through the ownCloud directory and loads all apps
97
	 * it can find. A directory contains an app if the file /appinfo/info.xml
98
	 * exists.
99
	 *
100
	 * if $types is set, only apps of those types will be loaded
101
	 */
102
	public static function loadApps($types = null) {
103
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
104
			return false;
105
		}
106
		// Load the enabled apps here
107
		$apps = self::getEnabledApps();
108
109
		// Add each apps' folder as allowed class path
110
		foreach($apps as $app) {
111
			$path = self::getAppPath($app);
112
			if($path !== false) {
113
				self::registerAutoloading($app, $path);
114
			}
115
		}
116
117
		// prevent app.php from printing output
118
		ob_start();
119
		foreach ($apps as $app) {
120
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
121
				self::loadApp($app);
122
			}
123
		}
124
		ob_end_clean();
125
126
		return true;
127
	}
128
129
	/**
130
	 * load a single app
131
	 *
132
	 * @param string $app
133
	 * @param bool $checkUpgrade whether an upgrade check should be done
134
	 * @throws \OC\NeedsUpdateException
135
	 */
136
	public static function loadApp($app, $checkUpgrade = true) {
137
		self::$loadedApps[] = $app;
138
		$appPath = self::getAppPath($app);
139
		if($appPath === false) {
140
			return;
141
		}
142
143
		// in case someone calls loadApp() directly
144
		self::registerAutoloading($app, $appPath);
145
146
		self::enableThemeIfApplicable($app);
147
148
		if (is_file($appPath . '/appinfo/app.php')) {
149
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
150
			if ($checkUpgrade and self::shouldUpgrade($app)) {
151
				throw new \OC\NeedsUpdateException();
152
			}
153
			self::requireAppFile($app);
154
			if (self::isType($app, ['authentication'])) {
155
				// since authentication apps affect the "is app enabled for group" check,
156
				// the enabled apps cache needs to be cleared to make sure that the
157
				// next time getEnableApps() is called it will also include apps that were
158
				// enabled for groups
159
				self::$enabledAppsCache = [];
160
			}
161
			\OC::$server->getEventLogger()->end('load_app_' . $app);
162
		}
163
	}
164
165
	/**
166
	 * Enables the app as a theme if it has the type "theme"
167
	 * @param $app
168
	 */
169
	private static function enableThemeIfApplicable($app) {
170
		if (self::isType($app, 'theme')) {
171
			/** @var \OC\Theme\ThemeService $themeManager */
172
			$themeManager = \OC::$server->query('ThemeService');
173
			$themeManager->setAppTheme($app);
174
		}
175
	}
176
177
	/**
178
	 * @internal
179
	 * @param string $app
180
	 * @param string $path
181
	 */
182
	public static function registerAutoloading($app, $path) {
183
		// Register on PSR-4 composer autoloader
184
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
185
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
186
		if (defined('PHPUNIT_RUN')) {
187
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
188
		}
189
190
		// Register on legacy autoloader
191
		\OC::$loader->addValidRoot($path);
192
	}
193
194
	/**
195
	 * Load app.php from the given app
196
	 *
197
	 * @param string $app app name
198
	 */
199
	private static function requireAppFile($app) {
200
		try {
201
			// encapsulated here to avoid variable scope conflicts
202
			require_once $app . '/appinfo/app.php';
203
		} catch (Exception $ex) {
204
			\OC::$server->getLogger()->logException($ex);
205
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
206
			if (!in_array($app, $blacklist)) {
207
				self::disable($app);
208
			}
209
		}
210
	}
211
212
	/**
213
	 * check if an app is of a specific type
214
	 *
215
	 * @param string $app
216
	 * @param string|array $types
217
	 * @return bool
218
	 */
219
	public static function isType($app, $types) {
220
		if (is_string($types)) {
221
			$types = [$types];
222
		}
223
		$appTypes = self::getAppTypes($app);
224
		foreach ($types as $type) {
225
			if (array_search($type, $appTypes) !== false) {
226
				return true;
227
			}
228
		}
229
		return false;
230
	}
231
232
	/**
233
	 * get the types of an app
234
	 *
235
	 * @param string $app
236
	 * @return array
237
	 */
238
	private static function getAppTypes($app) {
239
		//load the cache
240
		if (count(self::$appTypes) == 0) {
241
			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
0 ignored issues
show
Documentation Bug introduced by
It seems like \OC::$server->getAppConf...tValues(false, 'types') can also be of type false. However, the property $appTypes is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
242
		}
243
244
		if (isset(self::$appTypes[$app])) {
245
			return explode(',', self::$appTypes[$app]);
246
		} else {
247
			return [];
248
		}
249
	}
250
251
	/**
252
	 * read app types from info.xml and cache them in the database
253
	 */
254
	public static function setAppTypes($app) {
255
		$appData = self::getAppInfo($app);
256
		if(!is_array($appData)) {
257
			return;
258
		}
259
260
		if (isset($appData['types'])) {
261
			$appTypes = implode(',', $appData['types']);
262
		} else {
263
			$appTypes = '';
264
		}
265
266
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
267
	}
268
269
	/**
270
	 * check if app is shipped
271
	 *
272
	 * @param string $appId the id of the app to check
273
	 * @return bool
274
	 *
275
	 * Check if an app that is installed is a shipped app or installed from the appstore.
276
	 */
277
	public static function isShipped($appId) {
278
		return \OC::$server->getAppManager()->isShipped($appId);
279
	}
280
281
	/**
282
	 * get all enabled apps
283
	 */
284
	protected static $enabledAppsCache = [];
285
286
	/**
287
	 * Returns apps enabled for the current user.
288
	 *
289
	 * @param bool $forceRefresh whether to refresh the cache
290
	 * @param bool $all whether to return apps for all users, not only the
291
	 * currently logged in one
292
	 * @return string[]
293
	 */
294
	public static function getEnabledApps($forceRefresh = false, $all = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $forceRefresh is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
295
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
296
			return [];
297
		}
298
		// in incognito mode or when logged out, $user will be false,
299
		// which is also the case during an upgrade
300
		$appManager = \OC::$server->getAppManager();
301
		if ($all) {
302
			$user = null;
303
		} else {
304
			$user = \OC::$server->getUserSession()->getUser();
305
		}
306
307
		if (is_null($user)) {
308
			$apps = $appManager->getInstalledApps();
309
		} else {
310
			$apps = $appManager->getEnabledAppsForUser($user);
311
		}
312
		$apps = array_filter($apps, function ($app) {
313
			return $app !== 'files';//we add this manually
314
		});
315
		sort($apps);
316
		array_unshift($apps, 'files');
317
		return $apps;
318
	}
319
320
	/**
321
	 * checks whether or not an app is enabled
322
	 *
323
	 * @param string $app app
324
	 * @return bool
325
	 *
326
	 * This function checks whether or not an app is enabled.
327
	 */
328
	public static function isEnabled($app) {
329
		return \OC::$server->getAppManager()->isEnabledForUser($app);
330
	}
331
332
	/**
333
	 * enables an app
334
	 *
335
	 * @param mixed $app app
336
	 * @param array $groups (optional) when set, only these groups will have access to the app
337
	 * @throws \Exception
338
	 * @return void
339
	 *
340
	 * This function set an app as enabled in appconfig.
341
	 */
342
	public static function enable($app, $groups = null) {
343
		self::$enabledAppsCache = []; // flush
344
		if (!Installer::isInstalled($app)) {
345
			$app = self::installApp($app);
346
		} else {
347
			// check for required dependencies
348
			$config = \OC::$server->getConfig();
349
			$l = \OC::$server->getL10N('core');
350
			$info = self::getAppInfo($app);
351
352
			self::checkAppDependencies($config, $l, $info);
353
		}
354
355
		$appManager = \OC::$server->getAppManager();
356
		if (!is_null($groups)) {
357
			$groupManager = \OC::$server->getGroupManager();
358
			$groupsList = [];
359
			foreach ($groups as $group) {
360
				$groupItem = $groupManager->get($group);
361
				if ($groupItem instanceof \OCP\IGroup) {
362
					$groupsList[] = $groupManager->get($group);
363
				}
364
			}
365
			$appManager->enableAppForGroups($app, $groupsList);
366
		} else {
367
			$appManager->enableApp($app);
368
		}
369
	}
370
371
	/**
372
	 * @param string $app
373
	 * @return int
374
	 */
375
	private static function downloadApp($app) {
376
		$ocsClient = new OCSClient(
377
			\OC::$server->getHTTPClientService(),
378
			\OC::$server->getConfig(),
379
			\OC::$server->getLogger()
380
		);
381
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
382
		$download = $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion());
383
		if(isset($download['downloadlink']) and $download['downloadlink']!='') {
384
			// Replace spaces in download link without encoding entire URL
385
			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
386
			$info = ['source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData];
387
			$app = Installer::installApp($info);
388
		}
389
		return $app;
390
	}
391
392
	/**
393
	 * @param string $app
394
	 * @return bool
395
	 */
396
	public static function removeApp($app) {
397
		if (self::isShipped($app)) {
398
			return false;
399
		}
400
401
		return Installer::removeApp($app);
402
	}
403
404
	/**
405
	 * This function set an app as disabled in appconfig.
406
	 *
407
	 * @param string $app app
408
	 * @throws Exception
409
	 */
410
	public static function disable($app) {
411
		// Convert OCS ID to regular application identifier
412
		if(self::getInternalAppIdByOcs($app) !== false) {
413
			$app = self::getInternalAppIdByOcs($app);
414
		}
415
416
		// flush
417
		self::$enabledAppsCache = [];
418
419
		// run uninstall steps
420
		$appData = OC_App::getAppInfo($app);
421
		if (!is_null($appData)) {
422
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
423
		}
424
425
		// emit disable hook - needed anymore ?
426
		\OC_Hook::emit('OC_App', 'pre_disable', ['app' => $app]);
427
428
		// finally disable it
429
		$appManager = \OC::$server->getAppManager();
430
		$appManager->disableApp($app);
431
	}
432
433
	/**
434
	 * Returns the Settings Navigation
435
	 *
436
	 * @return string[]
437
	 *
438
	 * This function returns an array containing all settings pages added. The
439
	 * entries are sorted by the key 'order' ascending.
440
	 */
441
	public static function getSettingsNavigation() {
442
		$l = \OC::$server->getL10N('lib');
443
		$urlGenerator = \OC::$server->getURLGenerator();
444
445
		$settings = [];
446
		// by default, settings only contain the help menu
447
		if (OC_Util::getEditionString() === '' &&
448
			\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true
449
		) {
450
			$settings = [
451
				[
452
					"id" => "help",
453
					"order" => 1000,
454
					"href" => $urlGenerator->linkToRoute('settings_help'),
455
					"name" => $l->t("Help"),
456
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
457
				]
458
			];
459
		}
460
461
		// if the user is logged-in
462
		if (OC_User::isLoggedIn()) {
463
			// personal menu
464
			$settings[] = [
465
				"id" => "personal",
466
				"order" => 1,
467
				"href" => $urlGenerator->linkToRoute('settings_personal'),
468
				"name" => $l->t("Personal"),
469
				"icon" => $urlGenerator->imagePath("settings", "personal.svg")
470
			];
471
472
			//SubAdmins are also allowed to access user management
473
			$userObject = \OC::$server->getUserSession()->getUser();
474
			$isSubAdmin = false;
475
			if($userObject !== null) {
476
				$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
477
			}
478
			if ($isSubAdmin) {
479
				// admin users menu
480
				$settings[] = [
481
					"id" => "core_users",
482
					"order" => 2,
483
					"href" => $urlGenerator->linkToRoute('settings_users'),
484
					"name" => $l->t("Users"),
485
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
486
				];
487
			}
488
489
			// if the user is an admin
490
			if (OC_User::isAdminUser(OC_User::getUser())) {
491
				// admin settings
492
				$settings[] = [
493
					"id" => "admin",
494
					"order" => 1000,
495
					"href" => $urlGenerator->linkToRoute('settings_admin'),
496
					"name" => $l->t("Admin"),
497
					"icon" => $urlGenerator->imagePath("settings", "admin.svg")
498
				];
499
			}
500
		}
501
502
		$navigation = self::proceedNavigation($settings);
503
		return $navigation;
504
	}
505
506
	// This is private as well. It simply works, so don't ask for more details
507
	private static function proceedNavigation($list) {
508
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
509
		foreach ($list as &$navEntry) {
510
			if ($navEntry['id'] == $activeApp) {
511
				$navEntry['active'] = true;
512
			} else {
513
				$navEntry['active'] = false;
514
			}
515
		}
516
		unset($navEntry);
517
518
		usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}'));
519
520
		return $list;
521
	}
522
523
	/**
524
	 * Get the path where to install apps
525
	 *
526
	 * @return string|false
527
	 */
528
	public static function getInstallPath() {
529
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
530
			return false;
531
		}
532
533
		foreach (OC::$APPSROOTS as $dir) {
534
			if (isset($dir['writable']) && $dir['writable'] === true) {
535
				return $dir['path'];
536
			}
537
		}
538
539
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
540
		return null;
541
	}
542
543
544
	/**
545
	 * search for an app in all app-directories
546
	 *
547
	 * @param string $appId
548
	 * @return false|string
549
	 */
550
	protected static function findAppInDirectories($appId) {
551
		$sanitizedAppId = self::cleanAppId($appId);
552
		if($sanitizedAppId !== $appId) {
553
			return false;
554
		}
555
		static $app_dir = [];
556
557
		if (isset($app_dir[$appId])) {
558
			return $app_dir[$appId];
559
		}
560
561
		$possibleApps = [];
562
		foreach (OC::$APPSROOTS as $dir) {
563
			if (file_exists($dir['path'] . '/' . $appId)) {
564
				$possibleApps[] = $dir;
565
			}
566
		}
567
568
		if (empty($possibleApps)) {
569
			return false;
570
		} elseif (count($possibleApps) === 1) {
571
			$dir = array_shift($possibleApps);
572
			$app_dir[$appId] = $dir;
573
			return $dir;
574
		} else {
575
			$versionToLoad = [];
576
			foreach ($possibleApps as $possibleApp) {
577
				$version = self::getAppVersionByPath($possibleApp['path']);
578
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
579
					$versionToLoad = [
580
						'dir' => $possibleApp,
581
						'version' => $version,
582
					];
583
				}
584
			}
585
			$app_dir[$appId] = $versionToLoad['dir'];
586
			return $versionToLoad['dir'];
587
			//TODO - write test
588
		}
589
	}
590
591
	/**
592
	 * Get the directory for the given app.
593
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
594
	 *
595
	 * @param string $appId
596
	 * @return string|false
597
	 */
598
	public static function getAppPath($appId) {
599
		if ($appId === null || trim($appId) === '') {
600
			return false;
601
		}
602
603
		if (($dir = self::findAppInDirectories($appId)) != false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $dir = self::findAppInDirectories($appId) of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
604
			return $dir['path'] . '/' . $appId;
605
		}
606
		return false;
607
	}
608
609
610
	/**
611
	 * check if an app's directory is writable
612
	 *
613
	 * @param string $appId
614
	 * @return bool
615
	 */
616
	public static function isAppDirWritable($appId) {
617
		$path = self::getAppPath($appId);
618
		return ($path !== false) ? is_writable($path) : false;
619
	}
620
621
	/**
622
	 * Get the path for the given app on the access
623
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
624
	 *
625
	 * @param string $appId
626
	 * @return string|false
627
	 */
628
	public static function getAppWebPath($appId) {
629
		if (($dir = self::findAppInDirectories($appId)) != false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $dir = self::findAppInDirectories($appId) of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
630
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
631
		}
632
		return false;
633
	}
634
635
	/**
636
	 * get the last version of the app from appinfo/info.xml
637
	 *
638
	 * @param string $appId
639
	 * @return string
640
	 */
641
	public static function getAppVersion($appId) {
642
		if (!isset(self::$appVersion[$appId])) {
643
			$file = self::getAppPath($appId);
644
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
645
		}
646
		return self::$appVersion[$appId];
647
	}
648
649
	/**
650
	 * get app's version based on it's path
651
	 *
652
	 * @param string $path
653
	 * @return string
654
	 */
655
	public static function getAppVersionByPath($path) {
656
		$infoFile = $path . '/appinfo/info.xml';
657
		$appData = self::getAppInfo($infoFile, true);
658
		return isset($appData['version']) ? $appData['version'] : '';
659
	}
660
661
662
	/**
663
	 * Read all app metadata from the info.xml file
664
	 *
665
	 * @param string $appId id of the app or the path of the info.xml file
666
	 * @param boolean $path (optional)
667
	 * @return array|null
668
	 * @note all data is read from info.xml, not just pre-defined fields
669
	 */
670
	public static function getAppInfo($appId, $path = false) {
671
		if ($path) {
672
			$file = $appId;
673
		} else {
674
			if (isset(self::$appInfo[$appId])) {
675
				return self::$appInfo[$appId];
676
			}
677
			$appPath = self::getAppPath($appId);
678
			if($appPath === false) {
679
				return null;
680
			}
681
			$file = $appPath . '/appinfo/info.xml';
682
		}
683
684
		$parser = new InfoParser();
685
		$data = $parser->parse($file);
0 ignored issues
show
Bug Compatibility introduced by
The expression $parser->parse($file); of type null|string|array adds the type string to the return on line 699 which is incompatible with the return type documented by OC_App::getAppInfo of type array|null.
Loading history...
686
687
		if (is_array($data)) {
688
			$data = OC_App::parseAppInfo($data);
689
		}
690
		if(isset($data['ocsid'])) {
691
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
692
			if($storedId !== '' && $storedId !== $data['ocsid']) {
693
				$data['ocsid'] = $storedId;
694
			}
695
		}
696
697
		self::$appInfo[$appId] = $data;
698
699
		return $data;
700
	}
701
702
	/**
703
	 * Returns the navigation
704
	 *
705
	 * @return array
706
	 *
707
	 * This function returns an array containing all entries added. The
708
	 * entries are sorted by the key 'order' ascending. Additional to the keys
709
	 * given for each app the following keys exist:
710
	 *   - active: boolean, signals if the user is on this navigation entry
711
	 */
712
	public static function getNavigation() {
713
		$entries = OC::$server->getNavigationManager()->getAll();
714
		$navigation = self::proceedNavigation($entries);
715
		return $navigation;
716
	}
717
718
	/**
719
	 * get the id of loaded app
720
	 *
721
	 * @return string
722
	 */
723
	public static function getCurrentApp() {
724
		$request = \OC::$server->getRequest();
725
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
726
		$topFolder = substr($script, 0, strpos($script, '/'));
727
		if (empty($topFolder)) {
728
			$path_info = $request->getPathInfo();
729
			if ($path_info) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path_info of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
730
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
731
			}
732
		}
733
		if ($topFolder == 'apps') {
734
			$length = strlen($topFolder);
735
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
736
		} else {
737
			return $topFolder;
738
		}
739
	}
740
741
	/**
742
	 * @param string $type
743
	 * @return array
744
	 */
745
	public static function getForms($type) {
746
		$forms = [];
747
		switch ($type) {
748
			case 'admin':
749
				$source = self::$adminForms;
750
				break;
751
			case 'personal':
752
				$source = self::$personalForms;
753
				break;
754
			default:
755
				return [];
756
		}
757
		foreach ($source as $form) {
758
			$page = include $form['page'];
759
			$forms[] = [
760
				'appId' => $form['appId'],
761
				'page' => $page,
762
			];
763
		}
764
		return $forms;
765
	}
766
767
	/**
768
	 * register an admin form to be shown
769
	 *
770
	 * @param string $app
771
	 * @param string $page
772
	 */
773
	public static function registerAdmin($app, $page) {
774
		self::$adminForms[] = [
775
			'appId' => $app,
776
			'page' => $app . '/' . $page . '.php',
777
		];
778
	}
779
780
	/**
781
	 * register a personal form to be shown
782
	 * @param string $app
783
	 * @param string $page
784
	 */
785
	public static function registerPersonal($app, $page) {
786
		self::$personalForms[] = [
787
			'appId' => $app,
788
			'page' => $app . '/' . $page . '.php',
789
		];
790
	}
791
792
	/**
793
	 * @param array $entry
794
	 */
795
	public static function registerLogIn(array $entry) {
796
		self::$altLogin[] = $entry;
797
	}
798
799
	/**
800
	 * @return array
801
	 */
802
	public static function getAlternativeLogIns() {
803
		return self::$altLogin;
804
	}
805
806
	/**
807
	 * get a list of all apps in the apps folder
808
	 *
809
	 * @return array an array of app names (string IDs)
810
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
811
	 */
812
	public static function getAllApps() {
813
814
		$apps = [];
815
816
		foreach (OC::$APPSROOTS as $apps_dir) {
817
			if (!is_readable($apps_dir['path'])) {
818
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
819
				continue;
820
			}
821
			$dh = opendir($apps_dir['path']);
822
823
			if (is_resource($dh)) {
824
				while (($file = readdir($dh)) !== false) {
825
826
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
827
828
						$apps[] = $file;
829
					}
830
				}
831
			}
832
		}
833
834
		return $apps;
835
	}
836
837
	/**
838
	 * List all apps, this is used in apps.php
839
	 *
840
	 * @param bool $onlyLocal
841
	 * @param bool $includeUpdateInfo Should we check whether there is an update
842
	 *                                in the app store?
843
	 * @param OCSClient $ocsClient
844
	 * @return array
845
	 */
846
	public static function listAllApps($onlyLocal = false,
847
									   $includeUpdateInfo = true,
848
									   OCSClient $ocsClient) {
849
		$installedApps = OC_App::getAllApps();
850
851
		//TODO which apps do we want to blacklist and how do we integrate
852
		// blacklisting with the multi apps folder feature?
853
854
		//we don't want to show configuration for these
855
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
856
		$appList = [];
857
		$urlGenerator = \OC::$server->getURLGenerator();
858
859
		foreach ($installedApps as $app) {
860
			if (array_search($app, $blacklist) === false) {
861
862
				$info = OC_App::getAppInfo($app);
863
				if (!is_array($info)) {
864
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
865
					continue;
866
				}
867
868
				if (!isset($info['name'])) {
869
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
870
					continue;
871
				}
872
873
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
874
				$info['groups'] = null;
875
				if ($enabled === 'yes') {
876
					$active = true;
877
				} else if ($enabled === 'no') {
878
					$active = false;
879
				} else {
880
					$active = true;
881
					$info['groups'] = $enabled;
882
				}
883
884
				$info['active'] = $active;
885
886
				if (self::isShipped($app)) {
887
					$info['internal'] = true;
888
					$info['level'] = self::officialApp;
889
					$info['removable'] = false;
890
				} else {
891
					$info['internal'] = false;
892
					$info['removable'] = true;
893
				}
894
895
				$info['update'] = ($includeUpdateInfo) ? Installer::isUpdateAvailable($app) : null;
896
897
				$appPath = self::getAppPath($app);
898
				if($appPath !== false) {
899
					$appIcon = $appPath . '/img/' . $app . '.svg';
900
					if (file_exists($appIcon)) {
901
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
902
						$info['previewAsIcon'] = true;
903
					} else {
904
						$appIcon = $appPath . '/img/app.svg';
905
						if (file_exists($appIcon)) {
906
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
907
							$info['previewAsIcon'] = true;
908
						}
909
					}
910
				}
911
				// fix documentation
912
				if (isset($info['documentation']) && is_array($info['documentation'])) {
913
					foreach ($info['documentation'] as $key => $url) {
914
						// If it is not an absolute URL we assume it is a key
915
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
916
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
917
							$url = $urlGenerator->linkToDocs($url);
918
						}
919
920
						$info['documentation'][$key] = $url;
921
					}
922
				}
923
924
				$info['version'] = OC_App::getAppVersion($app);
925
				$appList[] = $info;
926
			}
927
		}
928
		if ($onlyLocal) {
929
			$remoteApps = [];
930
		} else {
931
			$remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient);
932
		}
933
		if ($remoteApps) {
934
			// Remove duplicates
935
			foreach ($appList as $app) {
936
				foreach ($remoteApps AS $key => $remote) {
937
					if ($app['name'] === $remote['name'] ||
938
						(isset($app['ocsid']) &&
939
							$app['ocsid'] === $remote['id'])
940
					) {
941
						unset($remoteApps[$key]);
942
					}
943
				}
944
			}
945
			$combinedApps = array_merge($appList, $remoteApps);
946
		} else {
947
			$combinedApps = $appList;
948
		}
949
950
		return $combinedApps;
951
	}
952
953
	/**
954
	 * Returns the internal app ID or false
955
	 * @param string $ocsID
956
	 * @return string|false
957
	 */
958
	public static function getInternalAppIdByOcs($ocsID) {
959
		if(is_numeric($ocsID)) {
960
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
961
			if(array_search($ocsID, $idArray)) {
962
				return array_search($ocsID, $idArray);
963
			}
964
		}
965
		return false;
966
	}
967
968
	/**
969
	 * Get a list of all apps on the appstore
970
	 * @param string $filter
971
	 * @param string|null $category
972
	 * @param OCSClient $ocsClient
973
	 * @return array|bool  multi-dimensional array of apps.
974
	 *                     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
975
	 */
976
	public static function getAppstoreApps($filter = 'approved',
977
										   $category = null,
978
										   OCSClient $ocsClient) {
979
		$categories = [$category];
980
981
		if (is_null($category)) {
982
			$categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion());
983
			if (is_array($categoryNames)) {
984
				// Check that categories of apps were retrieved correctly
985
				if (!$categories = array_keys($categoryNames)) {
986
					return false;
987
				}
988
			} else {
989
				return false;
990
			}
991
		}
992
993
		$page = 0;
994
		$remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion());
995
		$apps = [];
996
		$i = 0;
997
		$l = \OC::$server->getL10N('core');
998
		foreach ($remoteApps as $app) {
999
			$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
1000
			// enhance app info (for example the description)
1001
			$apps[$i] = OC_App::parseAppInfo($app);
1002
			$apps[$i]['author'] = $app['personid'];
1003
			$apps[$i]['ocs_id'] = $app['id'];
1004
			$apps[$i]['internal'] = 0;
1005
			$apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
1006
			$apps[$i]['update'] = false;
1007
			$apps[$i]['groups'] = false;
1008
			$apps[$i]['score'] = $app['score'];
1009
			$apps[$i]['removable'] = false;
1010
			if ($app['label'] == 'recommended') {
1011
				$apps[$i]['internallabel'] = (string)$l->t('Recommended');
1012
				$apps[$i]['internalclass'] = 'recommendedapp';
1013
			}
1014
1015
			// Apps from the appstore are always assumed to be compatible with the
1016
			// the current release as the initial filtering is done on the appstore
1017
			$apps[$i]['dependencies']['owncloud']['@attributes']['min-version'] = implode('.', \OCP\Util::getVersion());
1018
			$apps[$i]['dependencies']['owncloud']['@attributes']['max-version'] = implode('.', \OCP\Util::getVersion());
1019
1020
			$i++;
1021
		}
1022
1023
1024
1025
		if (empty($apps)) {
1026
			return false;
1027
		} else {
1028
			return $apps;
1029
		}
1030
	}
1031
1032
	public static function shouldUpgrade($app) {
1033
		$versions = self::getAppVersions();
1034
		$currentVersion = OC_App::getAppVersion($app);
1035
		if ($currentVersion && isset($versions[$app])) {
1036
			$installedVersion = $versions[$app];
1037
			if (!version_compare($currentVersion, $installedVersion, '=')) {
1038
				return true;
1039
			}
1040
		}
1041
		return false;
1042
	}
1043
1044
	/**
1045
	 * Adjust the number of version parts of $version1 to match
1046
	 * the number of version parts of $version2.
1047
	 *
1048
	 * @param string $version1 version to adjust
1049
	 * @param string $version2 version to take the number of parts from
1050
	 * @return string shortened $version1
1051
	 */
1052
	private static function adjustVersionParts($version1, $version2) {
1053
		$version1 = explode('.', $version1);
1054
		$version2 = explode('.', $version2);
1055
		// reduce $version1 to match the number of parts in $version2
1056
		while (count($version1) > count($version2)) {
1057
			array_pop($version1);
1058
		}
1059
		// if $version1 does not have enough parts, add some
1060
		while (count($version1) < count($version2)) {
1061
			$version1[] = '0';
1062
		}
1063
		return implode('.', $version1);
1064
	}
1065
1066
	/**
1067
	 * Check whether the current ownCloud version matches the given
1068
	 * application's version requirements.
1069
	 *
1070
	 * The comparison is made based on the number of parts that the
1071
	 * app info version has. For example for ownCloud 6.0.3 if the
1072
	 * app info version is expecting version 6.0, the comparison is
1073
	 * made on the first two parts of the ownCloud version.
1074
	 * This means that it's possible to specify "requiremin" => 6
1075
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
1076
	 *
1077
	 * @param string $ocVersion ownCloud version to check against
1078
	 * @param array $appInfo app info (from xml)
1079
	 *
1080
	 * @return boolean true if compatible, otherwise false
1081
	 */
1082
	public static function isAppCompatible($ocVersion, $appInfo) {
1083
		$requireMin = '';
1084
		$requireMax = '';
1085
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
1086
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1087
		} else if (isset($appInfo['requiremin'])) {
1088
			$requireMin = $appInfo['requiremin'];
1089
		} else if (isset($appInfo['require'])) {
1090
			$requireMin = $appInfo['require'];
1091
		}
1092
1093
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
1094
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1095
		} else if (isset($appInfo['requiremax'])) {
1096
			$requireMax = $appInfo['requiremax'];
1097
		}
1098
1099
		if (is_array($ocVersion)) {
1100
			$ocVersion = implode('.', $ocVersion);
1101
		}
1102
1103 View Code Duplication
		if (!empty($requireMin)
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...
1104
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1105
		) {
1106
1107
			return false;
1108
		}
1109
1110 View Code Duplication
		if (!empty($requireMax)
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...
1111
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1112
		) {
1113
			return false;
1114
		}
1115
1116
		return true;
1117
	}
1118
1119
	/**
1120
	 * get the installed version of all apps
1121
	 */
1122
	public static function getAppVersions() {
1123
		static $versions;
1124
1125
		if(!$versions) {
1126
			$appConfig = \OC::$server->getAppConfig();
1127
			$versions = $appConfig->getValues(false, 'installed_version');
1128
		}
1129
		return $versions;
1130
	}
1131
1132
	/**
1133
	 * @param string $app
1134
	 * @return bool
1135
	 * @throws Exception if app is not compatible with this version of ownCloud
1136
	 * @throws Exception if no app-name was specified
1137
	 */
1138
	public static function installApp($app) {
1139
		$appName = $app; // $app will be overwritten, preserve name for error logging
1140
		$l = \OC::$server->getL10N('core');
1141
		$config = \OC::$server->getConfig();
1142
		$ocsClient = new OCSClient(
1143
			\OC::$server->getHTTPClientService(),
1144
			$config,
1145
			\OC::$server->getLogger()
1146
		);
1147
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
1148
1149
		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
1150
		if (!is_numeric($app)) {
1151
			$shippedVersion = self::getAppVersion($app);
1152
			if ($appData && version_compare($shippedVersion, $appData['version'], '<')) {
1153
				$app = self::downloadApp($app);
1154
			} else {
1155
				$app = Installer::installShippedApp($app);
1156
			}
1157
		} else {
1158
			// Maybe the app is already installed - compare the version in this
1159
			// case and use the local already installed one.
1160
			// FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me.
1161
			$internalAppId = self::getInternalAppIdByOcs($app);
1162
			if($internalAppId !== false) {
1163
				if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) {
1164
					$app = self::downloadApp($app);
1165
				} else {
1166
					self::enable($internalAppId);
1167
					$app = $internalAppId;
1168
				}
1169
			} else {
1170
				$app = self::downloadApp($app);
1171
			}
1172
		}
1173
1174
		if ($app !== false) {
1175
			// check if the app is compatible with this version of ownCloud
1176
			$info = self::getAppInfo($app);
1177
			if(!is_array($info)) {
1178
				throw new \Exception(
1179
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1180
						[$info['name']]
1181
					)
1182
				);
1183
			}
1184
1185
			$version = \OCP\Util::getVersion();
1186
			if (!self::isAppCompatible($version, $info)) {
0 ignored issues
show
Documentation introduced by
$version is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1187
				throw new \Exception(
1188
					$l->t('App "%s" cannot be installed because it is not compatible with this version of ownCloud.',
1189
						[$info['name']]
1190
					)
1191
				);
1192
			}
1193
1194
			// check for required dependencies
1195
			self::checkAppDependencies($config, $l, $info);
1196
1197
			$config->setAppValue($app, 'enabled', 'yes');
1198
			if (isset($appData['id'])) {
1199
				$config->setAppValue($app, 'ocsid', $appData['id']);
1200
			}
1201
			\OC_Hook::emit('OC_App', 'post_enable', ['app' => $app]);
1202
		} else {
1203
			if(empty($appName) ) {
1204
				throw new \Exception($l->t("No app name specified"));
1205
			} else {
1206
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
0 ignored issues
show
Documentation introduced by
$appName is of type string, but the function expects a array.

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...
1207
			}
1208
		}
1209
1210
		return $app;
1211
	}
1212
1213
	/**
1214
	 * update the database for the app and call the update script
1215
	 *
1216
	 * @param string $appId
1217
	 * @return bool
1218
	 */
1219
	public static function updateApp($appId) {
1220
		$appPath = self::getAppPath($appId);
1221
		if($appPath === false) {
1222
			return false;
1223
		}
1224
		$appData = self::getAppInfo($appId);
1225
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1226
		if (file_exists($appPath . '/appinfo/database.xml')) {
1227
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1228
		}
1229
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1230
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1231
		unset(self::$appVersion[$appId]);
1232
		// run upgrade code
1233
		if (file_exists($appPath . '/appinfo/update.php')) {
1234
			self::loadApp($appId, false);
1235
			include $appPath . '/appinfo/update.php';
1236
		}
1237
		self::setupBackgroundJobs($appData['background-jobs']);
1238
1239
		//set remote/public handlers
1240
		if (array_key_exists('ocsid', $appData)) {
1241
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1242 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
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...
1243
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1244
		}
1245 View Code Duplication
		foreach ($appData['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...
1246
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1247
		}
1248
		foreach ($appData['public'] as $name => $path) {
1249
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1250
		}
1251
1252
		self::setAppTypes($appId);
1253
1254
		$version = \OC_App::getAppVersion($appId);
1255
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
1256
1257
		return true;
1258
	}
1259
1260
	/**
1261
	 * @param string $appId
1262
	 * @param string[] $steps
1263
	 * @throws \OC\NeedsUpdateException
1264
	 */
1265
	public static function executeRepairSteps($appId, array $steps) {
1266
		if (empty($steps)) {
1267
			return;
1268
		}
1269
		// load the app
1270
		self::loadApp($appId, false);
1271
1272
		$dispatcher = OC::$server->getEventDispatcher();
1273
1274
		// load the steps
1275
		$r = new Repair([], $dispatcher);
0 ignored issues
show
Documentation introduced by
$dispatcher is of type object<Symfony\Component...entDispatcherInterface>, but the function expects a null|object<Symfony\Comp...atcher\EventDispatcher>.

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...
1276
		foreach ($steps as $step) {
1277
			try {
1278
				$r->addStep($step);
1279
			} catch (Exception $ex) {
1280
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1281
				\OC::$server->getLogger()->logException($ex);
1282
			}
1283
		}
1284
		// run the steps
1285
		$r->run();
1286
	}
1287
1288
	public static function setupBackgroundJobs(array $jobs) {
1289
		$queue = \OC::$server->getJobList();
1290
		foreach ($jobs as $job) {
1291
			$queue->add($job);
1292
		}
1293
	}
1294
1295
	/**
1296
	 * @param string $appId
1297
	 * @param string[] $steps
1298
	 */
1299
	private static function setupLiveMigrations($appId, array $steps) {
1300
		$queue = \OC::$server->getJobList();
1301
		foreach ($steps as $step) {
1302
			$queue->add('OC\Migration\BackgroundRepair', [
1303
				'app' => $appId,
1304
				'step' => $step]);
1305
		}
1306
	}
1307
1308
	/**
1309
	 * @param string $appId
1310
	 * @return \OC\Files\View|false
1311
	 */
1312
	public static function getStorage($appId) {
1313
		if (OC_App::isEnabled($appId)) { //sanity check
1314
			if (OC_User::isLoggedIn()) {
1315
				$view = new \OC\Files\View('/' . OC_User::getUser());
1316
				if (!$view->file_exists($appId)) {
1317
					$view->mkdir($appId);
1318
				}
1319
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1320
			} else {
1321
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1322
				return false;
1323
			}
1324
		} else {
1325
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1326
			return false;
1327
		}
1328
	}
1329
1330
	/**
1331
	 * parses the app data array and enhanced the 'description' value
1332
	 *
1333
	 * @param array $data the app data
1334
	 * @return array improved app data
1335
	 */
1336
	public static function parseAppInfo(array $data) {
1337
1338
		// just modify the description if it is available
1339
		// otherwise this will create a $data element with an empty 'description'
1340
		if (isset($data['description'])) {
1341
			if (is_string($data['description'])) {
1342
				// sometimes the description contains line breaks and they are then also
1343
				// shown in this way in the app management which isn't wanted as HTML
1344
				// manages line breaks itself
1345
1346
				// first of all we split on empty lines
1347
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1348
1349
				$result = [];
1350
				foreach ($paragraphs as $value) {
1351
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1352
					// with a single space - also trims whitespace
1353
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1354
				}
1355
1356
				// join the single paragraphs with a empty line in between
1357
				$data['description'] = implode("\n\n", $result);
1358
1359
			} else {
1360
				$data['description'] = '';
1361
			}
1362
		}
1363
1364
		return $data;
1365
	}
1366
1367
	/**
1368
	 * @param $config
1369
	 * @param $l
1370
	 * @param $info
1371
	 * @throws Exception
1372
	 */
1373
	protected static function checkAppDependencies($config, $l, $info) {
1374
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1375
		$missing = $dependencyAnalyzer->analyze($info);
1376
		if (!empty($missing)) {
1377
			$missingMsg = join(PHP_EOL, $missing);
1378
			throw new \Exception(
1379
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1380
					[$info['name'], $missingMsg]
1381
				)
1382
			);
1383
		}
1384
	}
1385
}
1386