Completed
Pull Request — master (#2092)
by Joas
08:22 queued 01:00
created

OC_App::findAppInDirectories()   D

Complexity

Conditions 10
Paths 17

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 27
nc 17
nop 1
dl 0
loc 40
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2016, Lukas Reschke <[email protected]>
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Björn Schießle <[email protected]>
10
 * @author Borjan Tchakaloff <[email protected]>
11
 * @author Brice Maron <[email protected]>
12
 * @author Christopher Schäpers <[email protected]>
13
 * @author Felix Moeller <[email protected]>
14
 * @author Frank Karlitschek <[email protected]>
15
 * @author Georg Ehrke <[email protected]>
16
 * @author Jakob Sack <[email protected]>
17
 * @author Jan-Christoph Borchardt <[email protected]>
18
 * @author Joas Schilling <[email protected]>
19
 * @author Jörn Friedrich Dreyer <[email protected]>
20
 * @author Kamil Domanski <[email protected]>
21
 * @author Klaas Freitag <[email protected]>
22
 * @author Lukas Reschke <[email protected]>
23
 * @author Markus Goetz <[email protected]>
24
 * @author Morris Jobke <[email protected]>
25
 * @author RealRancor <[email protected]>
26
 * @author Robin Appelman <[email protected]>
27
 * @author Robin McCorkell <[email protected]>
28
 * @author Roeland Jago Douma <[email protected]>
29
 * @author Sam Tuke <[email protected]>
30
 * @author Thomas Müller <[email protected]>
31
 * @author Thomas Tanghus <[email protected]>
32
 * @author Tom Needham <[email protected]>
33
 * @author Vincent Petry <[email protected]>
34
 *
35
 * @license AGPL-3.0
36
 *
37
 * This code is free software: you can redistribute it and/or modify
38
 * it under the terms of the GNU Affero General Public License, version 3,
39
 * as published by the Free Software Foundation.
40
 *
41
 * This program is distributed in the hope that it will be useful,
42
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44
 * GNU Affero General Public License for more details.
45
 *
46
 * You should have received a copy of the GNU Affero General Public License, version 3,
47
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
48
 *
49
 */
50
use OC\App\DependencyAnalyzer;
51
use OC\App\InfoParser;
52
use OC\App\Platform;
53
use OC\Installer;
54
use OC\OCSClient;
55
use OC\Repair;
56
use OCP\App\ManagerEvent;
57
58
/**
59
 * This class manages the apps. It allows them to register and integrate in the
60
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
61
 * upgrading and removing apps.
62
 */
63
class OC_App {
64
	static private $appVersion = [];
65
	static private $adminForms = array();
66
	static private $personalForms = array();
67
	static private $appInfo = array();
68
	static private $appTypes = array();
69
	static private $loadedApps = array();
70
	static private $altLogin = array();
71
	static private $alreadyRegistered = [];
72
	const officialApp = 200;
73
74
	/**
75
	 * clean the appId
76
	 *
77
	 * @param string|boolean $app AppId that needs to be cleaned
78
	 * @return string
79
	 */
80
	public static function cleanAppId($app) {
81
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
82
	}
83
84
	/**
85
	 * Check if an app is loaded
86
	 *
87
	 * @param string $app
88
	 * @return bool
89
	 */
90
	public static function isAppLoaded($app) {
91
		return in_array($app, self::$loadedApps, true);
92
	}
93
94
	/**
95
	 * loads all apps
96
	 *
97
	 * @param string[] | string | null $types
98
	 * @return bool
99
	 *
100
	 * This function walks through the ownCloud directory and loads all apps
101
	 * it can find. A directory contains an app if the file /appinfo/info.xml
102
	 * exists.
103
	 *
104
	 * if $types is set, only apps of those types will be loaded
105
	 */
106
	public static function loadApps($types = null) {
107
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
108
			return false;
109
		}
110
		// Load the enabled apps here
111
		$apps = self::getEnabledApps();
112
113
		// Add each apps' folder as allowed class path
114
		foreach($apps as $app) {
115
			$path = self::getAppPath($app);
116
			if($path !== false) {
117
				self::registerAutoloading($app, $path);
118
			}
119
		}
120
121
		// prevent app.php from printing output
122
		ob_start();
123
		foreach ($apps as $app) {
124
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
125
				self::loadApp($app);
126
			}
127
		}
128
		ob_end_clean();
129
130
		return true;
131
	}
132
133
	/**
134
	 * load a single app
135
	 *
136
	 * @param string $app
137
	 * @param bool $checkUpgrade whether an upgrade check should be done
138
	 * @throws \OC\NeedsUpdateException
139
	 */
140
	public static function loadApp($app, $checkUpgrade = true) {
141
		self::$loadedApps[] = $app;
142
		$appPath = self::getAppPath($app);
143
		if($appPath === false) {
144
			return;
145
		}
146
147
		// in case someone calls loadApp() directly
148
		self::registerAutoloading($app, $appPath);
149
150
		if (is_file($appPath . '/appinfo/app.php')) {
151
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
152
			if ($checkUpgrade and self::shouldUpgrade($app)) {
153
				throw new \OC\NeedsUpdateException();
154
			}
155
			self::requireAppFile($app);
156
			if (self::isType($app, array('authentication'))) {
157
				// since authentication apps affect the "is app enabled for group" check,
158
				// the enabled apps cache needs to be cleared to make sure that the
159
				// next time getEnableApps() is called it will also include apps that were
160
				// enabled for groups
161
				self::$enabledAppsCache = array();
162
			}
163
			\OC::$server->getEventLogger()->end('load_app_' . $app);
164
		}
165
166
		$info = self::getAppInfo($app);
167 View Code Duplication
		if (!empty($info['activity']['filters'])) {
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...
168
			foreach ($info['activity']['filters'] as $filter) {
169
				\OC::$server->getActivityManager()->registerFilter($filter);
170
			}
171
		}
172 View Code Duplication
		if (!empty($info['activity']['settings'])) {
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...
173
			foreach ($info['activity']['settings'] as $setting) {
174
				\OC::$server->getActivityManager()->registerSetting($setting);
175
			}
176
		}
177 View Code Duplication
		if (!empty($info['activity']['providers'])) {
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...
178
			foreach ($info['activity']['providers'] as $provider) {
179
				\OC::$server->getActivityManager()->registerProvider($provider);
180
			}
181
		}
182
	}
183
184
	/**
185
	 * @internal
186
	 * @param string $app
187
	 * @param string $path
188
	 */
189
	public static function registerAutoloading($app, $path) {
190
		$key = $app . '-' . $path;
191
		if(isset(self::$alreadyRegistered[$key])) {
192
			return;
193
		}
194
		self::$alreadyRegistered[$key] = true;
195
		// Register on PSR-4 composer autoloader
196
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
197
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
198
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
199
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
200
		}
201
202
		// Register on legacy autoloader
203
		\OC::$loader->addValidRoot($path);
204
	}
205
206
	/**
207
	 * Load app.php from the given app
208
	 *
209
	 * @param string $app app name
210
	 */
211
	private static function requireAppFile($app) {
212
		try {
213
			// encapsulated here to avoid variable scope conflicts
214
			require_once $app . '/appinfo/app.php';
215
		} catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error 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...
216
			\OC::$server->getLogger()->logException($ex);
217
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
218
			if (!in_array($app, $blacklist)) {
219
				self::disable($app);
220
			}
221
		}
222
	}
223
224
	/**
225
	 * check if an app is of a specific type
226
	 *
227
	 * @param string $app
228
	 * @param string|array $types
229
	 * @return bool
230
	 */
231
	public static function isType($app, $types) {
232
		if (is_string($types)) {
233
			$types = array($types);
234
		}
235
		$appTypes = self::getAppTypes($app);
236
		foreach ($types as $type) {
237
			if (array_search($type, $appTypes) !== false) {
238
				return true;
239
			}
240
		}
241
		return false;
242
	}
243
244
	/**
245
	 * get the types of an app
246
	 *
247
	 * @param string $app
248
	 * @return array
249
	 */
250
	private static function getAppTypes($app) {
251
		//load the cache
252
		if (count(self::$appTypes) == 0) {
253
			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...
254
		}
255
256
		if (isset(self::$appTypes[$app])) {
257
			return explode(',', self::$appTypes[$app]);
258
		} else {
259
			return array();
260
		}
261
	}
262
263
	/**
264
	 * read app types from info.xml and cache them in the database
265
	 */
266
	public static function setAppTypes($app) {
267
		$appData = self::getAppInfo($app);
268
		if(!is_array($appData)) {
269
			return;
270
		}
271
272
		if (isset($appData['types'])) {
273
			$appTypes = implode(',', $appData['types']);
274
		} else {
275
			$appTypes = '';
276
		}
277
278
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
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...
279
	}
280
281
	/**
282
	 * check if app is shipped
283
	 *
284
	 * @param string $appId the id of the app to check
285
	 * @return bool
286
	 *
287
	 * Check if an app that is installed is a shipped app or installed from the appstore.
288
	 */
289
	public static function isShipped($appId) {
290
		return \OC::$server->getAppManager()->isShipped($appId);
291
	}
292
293
	/**
294
	 * get all enabled apps
295
	 */
296
	protected static $enabledAppsCache = array();
297
298
	/**
299
	 * Returns apps enabled for the current user.
300
	 *
301
	 * @param bool $forceRefresh whether to refresh the cache
302
	 * @param bool $all whether to return apps for all users, not only the
303
	 * currently logged in one
304
	 * @return string[]
305
	 */
306
	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...
307
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
308
			return array();
309
		}
310
		// in incognito mode or when logged out, $user will be false,
311
		// which is also the case during an upgrade
312
		$appManager = \OC::$server->getAppManager();
313
		if ($all) {
314
			$user = null;
315
		} else {
316
			$user = \OC::$server->getUserSession()->getUser();
317
		}
318
319
		if (is_null($user)) {
320
			$apps = $appManager->getInstalledApps();
321
		} else {
322
			$apps = $appManager->getEnabledAppsForUser($user);
323
		}
324
		$apps = array_filter($apps, function ($app) {
325
			return $app !== 'files';//we add this manually
326
		});
327
		sort($apps);
328
		array_unshift($apps, 'files');
329
		return $apps;
330
	}
331
332
	/**
333
	 * checks whether or not an app is enabled
334
	 *
335
	 * @param string $app app
336
	 * @return bool
337
	 *
338
	 * This function checks whether or not an app is enabled.
339
	 */
340
	public static function isEnabled($app) {
341
		return \OC::$server->getAppManager()->isEnabledForUser($app);
342
	}
343
344
	/**
345
	 * enables an app
346
	 *
347
	 * @param string $appId
348
	 * @param array $groups (optional) when set, only these groups will have access to the app
349
	 * @throws \Exception
350
	 * @return void
351
	 *
352
	 * This function set an app as enabled in appconfig.
353
	 */
354
	public function enable($appId,
355
						   $groups = null) {
356
		self::$enabledAppsCache = []; // flush
357
		$l = \OC::$server->getL10N('core');
358
		$config = \OC::$server->getConfig();
359
360
		// Check if app is already downloaded
361
		$installer = new Installer(
362
			\OC::$server->getAppFetcher(),
363
			\OC::$server->getHTTPClientService(),
364
			\OC::$server->getTempManager(),
365
			\OC::$server->getLogger()
366
		);
367
		$isDownloaded = $installer->isDownloaded($appId);
368
369
		if(!$isDownloaded) {
370
			$installer->downloadApp($appId);
371
		}
372
373
		if (!Installer::isInstalled($appId)) {
374
			$appId = self::installApp(
375
				$appId,
376
				$config,
377
				$l
378
			);
379
			$installer->installApp($appId);
380
		} else {
381
			// check for required dependencies
382
			$info = self::getAppInfo($appId);
383
			self::checkAppDependencies($config, $l, $info);
384
			$installer->installApp($appId);
385
		}
386
387
		$appManager = \OC::$server->getAppManager();
388
		if (!is_null($groups)) {
389
			$groupManager = \OC::$server->getGroupManager();
390
			$groupsList = [];
391
			foreach ($groups as $group) {
392
				$groupItem = $groupManager->get($group);
393
				if ($groupItem instanceof \OCP\IGroup) {
394
					$groupsList[] = $groupManager->get($group);
395
				}
396
			}
397
			$appManager->enableAppForGroups($appId, $groupsList);
398
		} else {
399
			$appManager->enableApp($appId);
400
		}
401
402
		$info = self::getAppInfo($appId);
403 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
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...
404
			$appPath = self::getAppPath($appId);
405
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 404 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...
406
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
407
		}
408
	}
409
410
	/**
411
	 * @param string $app
412
	 * @return bool
413
	 */
414
	public static function removeApp($app) {
415
		if (self::isShipped($app)) {
416
			return false;
417
		}
418
419
		$installer = new Installer(
420
			\OC::$server->getAppFetcher(),
421
			\OC::$server->getHTTPClientService(),
422
			\OC::$server->getTempManager(),
423
			\OC::$server->getLogger()
424
		);
425
		return $installer->removeApp($app);
426
	}
427
428
	/**
429
	 * This function set an app as disabled in appconfig.
430
	 *
431
	 * @param string $app app
432
	 * @throws Exception
433
	 */
434
	public static function disable($app) {
435
		// flush
436
		self::$enabledAppsCache = array();
437
438
		// run uninstall steps
439
		$appData = OC_App::getAppInfo($app);
440
		if (!is_null($appData)) {
441
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
442
		}
443
444
		// emit disable hook - needed anymore ?
445
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
446
447
		// finally disable it
448
		$appManager = \OC::$server->getAppManager();
449
		$appManager->disableApp($app);
450
	}
451
452
	/**
453
	 * Returns the Settings Navigation
454
	 *
455
	 * @return string[]
456
	 *
457
	 * This function returns an array containing all settings pages added. The
458
	 * entries are sorted by the key 'order' ascending.
459
	 */
460
	public static function getSettingsNavigation() {
461
		$l = \OC::$server->getL10N('lib');
462
		$urlGenerator = \OC::$server->getURLGenerator();
463
464
		$settings = array();
465
		// by default, settings only contain the help menu
466
		if (\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true)) {
467
			$settings = array(
468
				array(
469
					"id" => "help",
470
					"order" => 4,
471
					"href" => $urlGenerator->linkToRoute('settings_help'),
472
					"name" => $l->t("Help"),
473
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
474
				)
475
			);
476
		}
477
478
		// if the user is logged-in
479
		if (OC_User::isLoggedIn()) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_User::isLoggedIn() has been deprecated with message: use \OC::$server->getUserSession()->isLoggedIn()

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...
480
			// personal menu
481
			$settings[] = array(
482
				"id" => "personal",
483
				"order" => 1,
484
				"href" => $urlGenerator->linkToRoute('settings_personal'),
485
				"name" => $l->t("Personal"),
486
				"icon" => $urlGenerator->imagePath("settings", "personal.svg")
487
			);
488
489
			//SubAdmins are also allowed to access user management
490
			$userObject = \OC::$server->getUserSession()->getUser();
491
			$isSubAdmin = false;
492
			if($userObject !== null) {
493
				$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
494
			}
495
			if ($isSubAdmin) {
496
				// admin users menu
497
				$settings[] = array(
498
					"id" => "core_users",
499
					"order" => 3,
500
					"href" => $urlGenerator->linkToRoute('settings_users'),
501
					"name" => $l->t("Users"),
502
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
503
				);
504
			}
505
506
			// if the user is an admin
507
			if (OC_User::isAdminUser(OC_User::getUser())) {
508
				// admin settings
509
				$settings[] = array(
510
					"id" => "admin",
511
					"order" => 2,
512
					"href" => $urlGenerator->linkToRoute('settings.AdminSettings.index'),
513
					"name" => $l->t("Admin"),
514
					"icon" => $urlGenerator->imagePath("settings", "admin.svg")
515
				);
516
			}
517
		}
518
519
		$navigation = self::proceedNavigation($settings);
520
		return $navigation;
521
	}
522
523
	// This is private as well. It simply works, so don't ask for more details
524
	private static function proceedNavigation($list) {
525
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
526
		foreach ($list as &$navEntry) {
527
			if ($navEntry['id'] == $activeApp) {
528
				$navEntry['active'] = true;
529
			} else {
530
				$navEntry['active'] = false;
531
			}
532
		}
533
		unset($navEntry);
534
535
		usort($list, function($a, $b) {
536
			if (isset($a['order']) && isset($b['order'])) {
537
				return ($a['order'] < $b['order']) ? -1 : 1;
538
			} else if (isset($a['order']) || isset($b['order'])) {
539
				return isset($a['order']) ? -1 : 1;
540
			} else {
541
				return ($a['name'] < $b['name']) ? -1 : 1;
542
			}
543
		});
544
545
		return $list;
546
	}
547
548
	/**
549
	 * Get the path where to install apps
550
	 *
551
	 * @return string|false
552
	 */
553
	public static function getInstallPath() {
554
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
555
			return false;
556
		}
557
558
		foreach (OC::$APPSROOTS as $dir) {
559
			if (isset($dir['writable']) && $dir['writable'] === true) {
560
				return $dir['path'];
561
			}
562
		}
563
564
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
565
		return null;
566
	}
567
568
569
	/**
570
	 * search for an app in all app-directories
571
	 *
572
	 * @param string $appId
573
	 * @return false|string
574
	 */
575
	public static function findAppInDirectories($appId) {
576
		$sanitizedAppId = self::cleanAppId($appId);
577
		if($sanitizedAppId !== $appId) {
578
			return false;
579
		}
580
		static $app_dir = array();
581
582
		if (isset($app_dir[$appId])) {
583
			return $app_dir[$appId];
584
		}
585
586
		$possibleApps = array();
587
		foreach (OC::$APPSROOTS as $dir) {
588
			if (file_exists($dir['path'] . '/' . $appId)) {
589
				$possibleApps[] = $dir;
590
			}
591
		}
592
593
		if (empty($possibleApps)) {
594
			return false;
595
		} elseif (count($possibleApps) === 1) {
596
			$dir = array_shift($possibleApps);
597
			$app_dir[$appId] = $dir;
598
			return $dir;
599
		} else {
600
			$versionToLoad = array();
601
			foreach ($possibleApps as $possibleApp) {
602
				$version = self::getAppVersionByPath($possibleApp['path']);
603
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
604
					$versionToLoad = array(
605
						'dir' => $possibleApp,
606
						'version' => $version,
607
					);
608
				}
609
			}
610
			$app_dir[$appId] = $versionToLoad['dir'];
611
			return $versionToLoad['dir'];
612
			//TODO - write test
613
		}
614
	}
615
616
	/**
617
	 * Get the directory for the given app.
618
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
619
	 *
620
	 * @param string $appId
621
	 * @return string|false
622
	 */
623
	public static function getAppPath($appId) {
624
		if ($appId === null || trim($appId) === '') {
625
			return false;
626
		}
627
628
		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...
629
			return $dir['path'] . '/' . $appId;
630
		}
631
		return false;
632
	}
633
634
	/**
635
	 * Get the path for the given app on the access
636
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
637
	 *
638
	 * @param string $appId
639
	 * @return string|false
640
	 */
641
	public static function getAppWebPath($appId) {
642
		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...
643
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
644
		}
645
		return false;
646
	}
647
648
	/**
649
	 * get the last version of the app from appinfo/info.xml
650
	 *
651
	 * @param string $appId
652
	 * @return string
653
	 */
654
	public static function getAppVersion($appId) {
655
		if (!isset(self::$appVersion[$appId])) {
656
			$file = self::getAppPath($appId);
657
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
658
		}
659
		return self::$appVersion[$appId];
660
	}
661
662
	/**
663
	 * get app's version based on it's path
664
	 *
665
	 * @param string $path
666
	 * @return string
667
	 */
668
	public static function getAppVersionByPath($path) {
669
		$infoFile = $path . '/appinfo/info.xml';
670
		$appData = self::getAppInfo($infoFile, true);
671
		return isset($appData['version']) ? $appData['version'] : '';
672
	}
673
674
675
	/**
676
	 * Read all app metadata from the info.xml file
677
	 *
678
	 * @param string $appId id of the app or the path of the info.xml file
679
	 * @param bool $path
680
	 * @param string $lang
681
	 * @return array|null
682
	 * @note all data is read from info.xml, not just pre-defined fields
683
	 */
684
	public static function getAppInfo($appId, $path = false, $lang = null) {
685
		if ($path) {
686
			$file = $appId;
687
		} else {
688
			if ($lang === null && isset(self::$appInfo[$appId])) {
689
				return self::$appInfo[$appId];
690
			}
691
			$appPath = self::getAppPath($appId);
692
			if($appPath === false) {
693
				return null;
694
			}
695
			$file = $appPath . '/appinfo/info.xml';
696
		}
697
698
		$parser = new InfoParser(\OC::$server->getMemCacheFactory()->create('core.appinfo'));
699
		$data = $parser->parse($file);
700
701
		if (is_array($data)) {
702
			$data = OC_App::parseAppInfo($data, $lang);
703
		}
704
		if(isset($data['ocsid'])) {
705
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
706
			if($storedId !== '' && $storedId !== $data['ocsid']) {
707
				$data['ocsid'] = $storedId;
708
			}
709
		}
710
711
		if ($lang === null) {
712
			self::$appInfo[$appId] = $data;
713
		}
714
715
		return $data;
716
	}
717
718
	/**
719
	 * Returns the navigation
720
	 *
721
	 * @return array
722
	 *
723
	 * This function returns an array containing all entries added. The
724
	 * entries are sorted by the key 'order' ascending. Additional to the keys
725
	 * given for each app the following keys exist:
726
	 *   - active: boolean, signals if the user is on this navigation entry
727
	 */
728
	public static function getNavigation() {
729
		$entries = OC::$server->getNavigationManager()->getAll();
730
		$navigation = self::proceedNavigation($entries);
731
		return $navigation;
732
	}
733
734
	/**
735
	 * get the id of loaded app
736
	 *
737
	 * @return string
738
	 */
739
	public static function getCurrentApp() {
740
		$request = \OC::$server->getRequest();
741
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
742
		$topFolder = substr($script, 0, strpos($script, '/'));
743
		if (empty($topFolder)) {
744
			$path_info = $request->getPathInfo();
745
			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...
746
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
747
			}
748
		}
749
		if ($topFolder == 'apps') {
750
			$length = strlen($topFolder);
751
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
752
		} else {
753
			return $topFolder;
754
		}
755
	}
756
757
	/**
758
	 * @param string $type
759
	 * @return array
760
	 */
761
	public static function getForms($type) {
762
		$forms = array();
763
		switch ($type) {
764
			case 'admin':
765
				$source = self::$adminForms;
766
				break;
767
			case 'personal':
768
				$source = self::$personalForms;
769
				break;
770
			default:
771
				return array();
772
		}
773
		foreach ($source as $form) {
774
			$forms[] = include $form;
775
		}
776
		return $forms;
777
	}
778
779
	/**
780
	 * register an admin form to be shown
781
	 *
782
	 * @param string $app
783
	 * @param string $page
784
	 */
785
	public static function registerAdmin($app, $page) {
786
		self::$adminForms[] = $app . '/' . $page . '.php';
787
	}
788
789
	/**
790
	 * register a personal form to be shown
791
	 * @param string $app
792
	 * @param string $page
793
	 */
794
	public static function registerPersonal($app, $page) {
795
		self::$personalForms[] = $app . '/' . $page . '.php';
796
	}
797
798
	/**
799
	 * @param array $entry
800
	 */
801
	public static function registerLogIn(array $entry) {
802
		self::$altLogin[] = $entry;
803
	}
804
805
	/**
806
	 * @return array
807
	 */
808
	public static function getAlternativeLogIns() {
809
		return self::$altLogin;
810
	}
811
812
	/**
813
	 * get a list of all apps in the apps folder
814
	 *
815
	 * @return array an array of app names (string IDs)
816
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
817
	 */
818
	public static function getAllApps() {
819
820
		$apps = array();
821
822
		foreach (OC::$APPSROOTS as $apps_dir) {
823
			if (!is_readable($apps_dir['path'])) {
824
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
825
				continue;
826
			}
827
			$dh = opendir($apps_dir['path']);
828
829
			if (is_resource($dh)) {
830
				while (($file = readdir($dh)) !== false) {
831
832
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
833
834
						$apps[] = $file;
835
					}
836
				}
837
			}
838
		}
839
840
		return $apps;
841
	}
842
843
	/**
844
	 * List all apps, this is used in apps.php
845
	 *
846
	 * @return array
847
	 */
848
	public function listAllApps() {
849
		$installedApps = OC_App::getAllApps();
850
851
		//we don't want to show configuration for these
852
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
853
		$appList = array();
854
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
855
		$urlGenerator = \OC::$server->getURLGenerator();
856
857
		foreach ($installedApps as $app) {
858
			if (array_search($app, $blacklist) === false) {
859
860
				$info = OC_App::getAppInfo($app, false, $langCode);
861
				if (!is_array($info)) {
862
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
863
					continue;
864
				}
865
866
				if (!isset($info['name'])) {
867
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
868
					continue;
869
				}
870
871
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
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...
872
				$info['groups'] = null;
873
				if ($enabled === 'yes') {
874
					$active = true;
875
				} else if ($enabled === 'no') {
876
					$active = false;
877
				} else {
878
					$active = true;
879
					$info['groups'] = $enabled;
880
				}
881
882
				$info['active'] = $active;
883
884
				if (self::isShipped($app)) {
885
					$info['internal'] = true;
886
					$info['level'] = self::officialApp;
887
					$info['removable'] = false;
888
				} else {
889
					$info['internal'] = false;
890
					$info['removable'] = true;
891
				}
892
893
				$appPath = self::getAppPath($app);
894
				if($appPath !== false) {
895
					$appIcon = $appPath . '/img/' . $app . '.svg';
896
					if (file_exists($appIcon)) {
897
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
898
						$info['previewAsIcon'] = true;
899
					} else {
900
						$appIcon = $appPath . '/img/app.svg';
901
						if (file_exists($appIcon)) {
902
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
903
							$info['previewAsIcon'] = true;
904
						}
905
					}
906
				}
907
				// fix documentation
908
				if (isset($info['documentation']) && is_array($info['documentation'])) {
909
					foreach ($info['documentation'] as $key => $url) {
910
						// If it is not an absolute URL we assume it is a key
911
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
912
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
913
							$url = $urlGenerator->linkToDocs($url);
914
						}
915
916
						$info['documentation'][$key] = $url;
917
					}
918
				}
919
920
				$info['version'] = OC_App::getAppVersion($app);
921
				$appList[] = $info;
922
			}
923
		}
924
925
		return $appList;
926
	}
927
928
	/**
929
	 * Returns the internal app ID or false
930
	 * @param string $ocsID
931
	 * @return string|false
932
	 */
933
	public static function getInternalAppIdByOcs($ocsID) {
934
		if(is_numeric($ocsID)) {
935
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
936
			if(array_search($ocsID, $idArray)) {
937
				return array_search($ocsID, $idArray);
938
			}
939
		}
940
		return false;
941
	}
942
943
	public static function shouldUpgrade($app) {
944
		$versions = self::getAppVersions();
945
		$currentVersion = OC_App::getAppVersion($app);
946
		if ($currentVersion && isset($versions[$app])) {
947
			$installedVersion = $versions[$app];
948
			if (!version_compare($currentVersion, $installedVersion, '=')) {
949
				return true;
950
			}
951
		}
952
		return false;
953
	}
954
955
	/**
956
	 * Adjust the number of version parts of $version1 to match
957
	 * the number of version parts of $version2.
958
	 *
959
	 * @param string $version1 version to adjust
960
	 * @param string $version2 version to take the number of parts from
961
	 * @return string shortened $version1
962
	 */
963
	private static function adjustVersionParts($version1, $version2) {
964
		$version1 = explode('.', $version1);
965
		$version2 = explode('.', $version2);
966
		// reduce $version1 to match the number of parts in $version2
967
		while (count($version1) > count($version2)) {
968
			array_pop($version1);
969
		}
970
		// if $version1 does not have enough parts, add some
971
		while (count($version1) < count($version2)) {
972
			$version1[] = '0';
973
		}
974
		return implode('.', $version1);
975
	}
976
977
	/**
978
	 * Check whether the current ownCloud version matches the given
979
	 * application's version requirements.
980
	 *
981
	 * The comparison is made based on the number of parts that the
982
	 * app info version has. For example for ownCloud 6.0.3 if the
983
	 * app info version is expecting version 6.0, the comparison is
984
	 * made on the first two parts of the ownCloud version.
985
	 * This means that it's possible to specify "requiremin" => 6
986
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
987
	 *
988
	 * @param string $ocVersion ownCloud version to check against
989
	 * @param array $appInfo app info (from xml)
990
	 *
991
	 * @return boolean true if compatible, otherwise false
992
	 */
993
	public static function isAppCompatible($ocVersion, $appInfo) {
994
		$requireMin = '';
995
		$requireMax = '';
996
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
997
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
998 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
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...
999
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1000
		} else if (isset($appInfo['requiremin'])) {
1001
			$requireMin = $appInfo['requiremin'];
1002
		} else if (isset($appInfo['require'])) {
1003
			$requireMin = $appInfo['require'];
1004
		}
1005
1006
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
1007
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
1008 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
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...
1009
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1010
		} else if (isset($appInfo['requiremax'])) {
1011
			$requireMax = $appInfo['requiremax'];
1012
		}
1013
1014
		if (is_array($ocVersion)) {
1015
			$ocVersion = implode('.', $ocVersion);
1016
		}
1017
1018 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...
1019
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1020
		) {
1021
1022
			return false;
1023
		}
1024
1025 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...
1026
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1027
		) {
1028
			return false;
1029
		}
1030
1031
		return true;
1032
	}
1033
1034
	/**
1035
	 * get the installed version of all apps
1036
	 */
1037
	public static function getAppVersions() {
1038
		static $versions;
1039
1040
		if(!$versions) {
1041
			$appConfig = \OC::$server->getAppConfig();
1042
			$versions = $appConfig->getValues(false, 'installed_version');
1043
		}
1044
		return $versions;
1045
	}
1046
1047
	/**
1048
	 * @param string $app
1049
	 * @param \OCP\IConfig $config
1050
	 * @param \OCP\IL10N $l
1051
	 * @return bool
1052
	 *
1053
	 * @throws Exception if app is not compatible with this version of ownCloud
1054
	 * @throws Exception if no app-name was specified
1055
	 */
1056
	public function installApp($app,
1057
							   \OCP\IConfig $config,
1058
							   \OCP\IL10N $l) {
1059
		if ($app !== false) {
1060
			// check if the app is compatible with this version of ownCloud
1061
			$info = self::getAppInfo($app);
1062
			if(!is_array($info)) {
1063
				throw new \Exception(
1064
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1065
						[$info['name']]
1066
					)
1067
				);
1068
			}
1069
1070
			$version = \OCP\Util::getVersion();
1071
			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...
1072
				throw new \Exception(
1073
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1074
						array($info['name'])
1075
					)
1076
				);
1077
			}
1078
1079
			// check for required dependencies
1080
			self::checkAppDependencies($config, $l, $info);
1081
1082
			$config->setAppValue($app, 'enabled', 'yes');
1083
			if (isset($appData['id'])) {
0 ignored issues
show
Bug introduced by
The variable $appData seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1084
				$config->setAppValue($app, 'ocsid', $appData['id']);
1085
			}
1086
1087 View Code Duplication
			if(isset($info['settings']) && is_array($info['settings'])) {
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...
1088
				$appPath = self::getAppPath($app);
1089
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1088 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...
1090
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1091
			}
1092
1093
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1094
		} else {
1095
			if(empty($appName) ) {
0 ignored issues
show
Bug introduced by
The variable $appName seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1096
				throw new \Exception($l->t("No app name specified"));
1097
			} else {
1098
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
1099
			}
1100
		}
1101
1102
		return $app;
1103
	}
1104
1105
	/**
1106
	 * update the database for the app and call the update script
1107
	 *
1108
	 * @param string $appId
1109
	 * @return bool
1110
	 */
1111
	public static function updateApp($appId) {
1112
		$appPath = self::getAppPath($appId);
1113
		if($appPath === false) {
1114
			return false;
1115
		}
1116
		$appData = self::getAppInfo($appId);
1117
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1118
		if (file_exists($appPath . '/appinfo/database.xml')) {
1119
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1120
		}
1121
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1122
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1123
		unset(self::$appVersion[$appId]);
1124
		// run upgrade code
1125
		if (file_exists($appPath . '/appinfo/update.php')) {
1126
			self::loadApp($appId, false);
1127
			include $appPath . '/appinfo/update.php';
1128
		}
1129
		self::setupBackgroundJobs($appData['background-jobs']);
1130 View Code Duplication
		if(isset($appData['settings']) && is_array($appData['settings'])) {
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...
1131
			$appPath = self::getAppPath($appId);
1132
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 1131 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...
1133
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1134
		}
1135
1136
		//set remote/public handlers
1137
		if (array_key_exists('ocsid', $appData)) {
1138
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1139 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...
1140
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1141
		}
1142
		foreach ($appData['remote'] as $name => $path) {
1143
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1144
		}
1145 View Code Duplication
		foreach ($appData['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...
1146
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1147
		}
1148
1149
		self::setAppTypes($appId);
1150
1151
		$version = \OC_App::getAppVersion($appId);
1152
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
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...
1153
1154
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1155
			ManagerEvent::EVENT_APP_UPDATE, $appId
1156
		));
1157
1158
		return true;
1159
	}
1160
1161
	/**
1162
	 * @param string $appId
1163
	 * @param string[] $steps
1164
	 * @throws \OC\NeedsUpdateException
1165
	 */
1166
	public static function executeRepairSteps($appId, array $steps) {
1167
		if (empty($steps)) {
1168
			return;
1169
		}
1170
		// load the app
1171
		self::loadApp($appId, false);
1172
1173
		$dispatcher = OC::$server->getEventDispatcher();
1174
1175
		// load the steps
1176
		$r = new Repair([], $dispatcher);
1177
		foreach ($steps as $step) {
1178
			try {
1179
				$r->addStep($step);
1180
			} catch (Exception $ex) {
1181
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1182
				\OC::$server->getLogger()->logException($ex);
1183
			}
1184
		}
1185
		// run the steps
1186
		$r->run();
1187
	}
1188
1189
	public static function setupBackgroundJobs(array $jobs) {
1190
		$queue = \OC::$server->getJobList();
1191
		foreach ($jobs as $job) {
1192
			$queue->add($job);
1193
		}
1194
	}
1195
1196
	/**
1197
	 * @param string $appId
1198
	 * @param string[] $steps
1199
	 */
1200
	private static function setupLiveMigrations($appId, array $steps) {
1201
		$queue = \OC::$server->getJobList();
1202
		foreach ($steps as $step) {
1203
			$queue->add('OC\Migration\BackgroundRepair', [
1204
				'app' => $appId,
1205
				'step' => $step]);
1206
		}
1207
	}
1208
1209
	/**
1210
	 * @param string $appId
1211
	 * @return \OC\Files\View|false
1212
	 */
1213
	public static function getStorage($appId) {
1214
		if (OC_App::isEnabled($appId)) { //sanity check
1215
			if (OC_User::isLoggedIn()) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_User::isLoggedIn() has been deprecated with message: use \OC::$server->getUserSession()->isLoggedIn()

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...
1216
				$view = new \OC\Files\View('/' . OC_User::getUser());
1217
				if (!$view->file_exists($appId)) {
1218
					$view->mkdir($appId);
1219
				}
1220
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1221
			} else {
1222
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1223
				return false;
1224
			}
1225
		} else {
1226
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1227
			return false;
1228
		}
1229
	}
1230
1231
	protected static function findBestL10NOption($options, $lang) {
1232
		$fallback = $similarLangFallback = $englishFallback = false;
1233
1234
		$lang = strtolower($lang);
1235
		$similarLang = $lang;
1236
		if (strpos($similarLang, '_')) {
1237
			// For "de_DE" we want to find "de" and the other way around
1238
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1239
		}
1240
1241
		foreach ($options as $option) {
1242
			if (is_array($option)) {
1243
				if ($fallback === false) {
1244
					$fallback = $option['@value'];
1245
				}
1246
1247
				if (!isset($option['@attributes']['lang'])) {
1248
					continue;
1249
				}
1250
1251
				$attributeLang = strtolower($option['@attributes']['lang']);
1252
				if ($attributeLang === $lang) {
1253
					return $option['@value'];
1254
				}
1255
1256
				if ($attributeLang === $similarLang) {
1257
					$similarLangFallback = $option['@value'];
1258
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1259
					if ($similarLangFallback === false) {
1260
						$similarLangFallback =  $option['@value'];
1261
					}
1262
				}
1263
			} else {
1264
				$englishFallback = $option;
1265
			}
1266
		}
1267
1268
		if ($similarLangFallback !== false) {
1269
			return $similarLangFallback;
1270
		} else if ($englishFallback !== false) {
1271
			return $englishFallback;
1272
		}
1273
		return (string) $fallback;
1274
	}
1275
1276
	/**
1277
	 * parses the app data array and enhanced the 'description' value
1278
	 *
1279
	 * @param array $data the app data
1280
	 * @param string $lang
1281
	 * @return array improved app data
1282
	 */
1283
	public static function parseAppInfo(array $data, $lang = null) {
1284
1285 View Code Duplication
		if ($lang && isset($data['name']) && is_array($data['name'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
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...
1286
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1287
		}
1288 View Code Duplication
		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
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...
1289
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1290
		}
1291 View Code Duplication
		if ($lang && isset($data['description']) && is_array($data['description'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
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...
1292
			$data['description'] = self::findBestL10NOption($data['description'], $lang);
1293
		}
1294
1295
		// just modify the description if it is available
1296
		// otherwise this will create a $data element with an empty 'description'
1297
		if (isset($data['description'])) {
1298
			if (is_string($data['description'])) {
1299
				// sometimes the description contains line breaks and they are then also
1300
				// shown in this way in the app management which isn't wanted as HTML
1301
				// manages line breaks itself
1302
1303
				// first of all we split on empty lines
1304
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1305
1306
				$result = [];
1307
				foreach ($paragraphs as $value) {
1308
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1309
					// with a single space - also trims whitespace
1310
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1311
				}
1312
1313
				// join the single paragraphs with a empty line in between
1314
				$data['description'] = implode("\n\n", $result);
1315
1316
			} else {
1317
				$data['description'] = '';
1318
			}
1319
		}
1320
1321
		return $data;
1322
	}
1323
1324
	/**
1325
	 * @param $config
1326
	 * @param $l
1327
	 * @param $info
1328
	 * @throws Exception
1329
	 */
1330
	protected static function checkAppDependencies($config, $l, $info) {
1331
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1332
		$missing = $dependencyAnalyzer->analyze($info);
1333
		if (!empty($missing)) {
1334
			$missingMsg = join(PHP_EOL, $missing);
1335
			throw new \Exception(
1336
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1337
					[$info['name'], $missingMsg]
1338
				)
1339
			);
1340
		}
1341
	}
1342
}
1343