Completed
Push — master ( e668be...f239ab )
by Thomas
15:59 queued 05:01
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

3 Methods

Rating   Name   Duplication   Size   Complexity  
A OC_App::getAppPath() 0 3 1
A OC_App::getAppWebPath() 0 3 1
A OC_App::isAppDirWritable() 0 4 2

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
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Bernhard Posselt <[email protected]>
6
 * @author Borjan Tchakaloff <[email protected]>
7
 * @author Brice Maron <[email protected]>
8
 * @author Christopher Schäpers <[email protected]>
9
 * @author Felix Moeller <[email protected]>
10
 * @author Frank Karlitschek <[email protected]>
11
 * @author Georg Ehrke <[email protected]>
12
 * @author Jakob Sack <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Jörn Friedrich Dreyer <[email protected]>
15
 * @author Kamil Domanski <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author Markus Goetz <[email protected]>
18
 * @author Morris Jobke <[email protected]>
19
 * @author Philipp Schaffrath <[email protected]>
20
 * @author ppaysant <[email protected]>
21
 * @author RealRancor <[email protected]>
22
 * @author Robin Appelman <[email protected]>
23
 * @author Robin McCorkell <[email protected]>
24
 * @author Roeland Jago Douma <[email protected]>
25
 * @author Sam Tuke <[email protected]>
26
 * @author Thomas Müller <[email protected]>
27
 * @author Thomas Tanghus <[email protected]>
28
 * @author Tom Needham <[email protected]>
29
 * @author Vincent Petry <[email protected]>
30
 *
31
 * @copyright Copyright (c) 2017, ownCloud GmbH
32
 * @license AGPL-3.0
33
 *
34
 * This code is free software: you can redistribute it and/or modify
35
 * it under the terms of the GNU Affero General Public License, version 3,
36
 * as published by the Free Software Foundation.
37
 *
38
 * This program is distributed in the hope that it will be useful,
39
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41
 * GNU Affero General Public License for more details.
42
 *
43
 * You should have received a copy of the GNU Affero General Public License, version 3,
44
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
45
 *
46
 */
47
use OC\App\DependencyAnalyzer;
48
use OC\App\InfoParser;
49
use OC\App\Platform;
50
use OC\Installer;
51
use OC\Repair;
52
53
/**
54
 * This class manages the apps. It allows them to register and integrate in the
55
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
56
 * upgrading and removing apps.
57
 */
58
class OC_App {
59
	static private $appVersion = [];
60
	static private $adminForms = [];
61
	static private $personalForms = [];
62
	static private $appInfo = [];
63
	static private $appTypes = [];
64
	static private $loadedApps = [];
65
	static private $loadedTypes = [];
66
	static private $altLogin = [];
67
	const officialApp = 200;
68
	const approvedApp = 100;
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 (is_array($types) && !array_diff($types, self::$loadedTypes)) {
104
			return true;
105
		}
106
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
107
			return false;
108
		}
109
		// Load the enabled apps here
110
		$apps = self::getEnabledApps();
111
112
		// Add each apps' folder as allowed class path
113
		foreach($apps as $app) {
114
			if (self::isAppLoaded($app)) {
115
				continue;
116
			}
117
			$path = self::getAppPath($app);
118
			if($path !== false) {
119
				self::registerAutoloading($app, $path);
120
			}
121
		}
122
123
		// prevent app.php from printing output
124
		ob_start();
125
		foreach ($apps as $app) {
126
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
127
				self::loadApp($app);
128
			}
129
		}
130
		ob_end_clean();
131
132
		// once all authentication apps are loaded we can validate the session
133
		if (is_null($types) || in_array('authentication', $types)) {
134
			if (\OC::$server->getUserSession()) {
135
				$request = \OC::$server->getRequest();
136
				$session = \OC::$server->getUserSession();
137
				$davUser = \OC::$server->getUserSession()->getSession()->get(\OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED);
138
				if (is_null($davUser)) {
139
					$session->validateSession();
140
				} else {
141
					/** @var \OC\Authentication\Token\DefaultTokenProvider $tokenProvider */
142
					$tokenProvider = \OC::$server->query('\OC\Authentication\Token\DefaultTokenProvider');
143
					$token = null;
144
					try {
145
						$token = $tokenProvider->getToken($session->getSession()->getId());
146
					} catch (\Exception $ex) {
147
						$password = null;
148
						if (isset($_SERVER['PHP_AUTH_PW'])) {
149
							$password = $_SERVER['PHP_AUTH_PW'];
150
						}
151
152
						$session->createSessionToken($request, $session->getUser()->getUID(), $session->getLoginName(), $password);
153
					}
154
155
					if ($token) {
156
						$tokenProvider->updateToken($token);
157
					}
158
				}
159
			}
160
		}
161
		if (is_array($types)) {
162
			self::$loadedTypes = array_merge(self::$loadedTypes, $types);
163
		}
164
165
		\OC_Hook::emit('OC_App', 'loadedApps');
166
		return true;
167
	}
168
169
	/**
170
	 * load a single app
171
	 *
172
	 * @param string $app
173
	 * @param bool $checkUpgrade whether an upgrade check should be done
174
	 * @throws \OC\NeedsUpdateException
175
	 */
176
	public static function loadApp($app, $checkUpgrade = true) {
177
		self::$loadedApps[] = $app;
178
		$appPath = self::getAppPath($app);
179
		if($appPath === false) {
180
			return;
181
		}
182
183
		// in case someone calls loadApp() directly
184
		self::registerAutoloading($app, $appPath);
185
186
		self::enableThemeIfApplicable($app);
187
188
		if (is_file($appPath . '/appinfo/app.php')) {
189
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
190
			if ($checkUpgrade and self::shouldUpgrade($app)) {
191
				throw new \OC\NeedsUpdateException();
192
			}
193
			self::requireAppFile($app);
194
			if (self::isType($app, ['authentication'])) {
195
				// since authentication apps affect the "is app enabled for group" check,
196
				// the enabled apps cache needs to be cleared to make sure that the
197
				// next time getEnableApps() is called it will also include apps that were
198
				// enabled for groups
199
				self::$enabledAppsCache = [];
200
			}
201
			\OC::$server->getEventLogger()->end('load_app_' . $app);
202
		}
203
	}
204
205
	/**
206
	 * Enables the app as a theme if it has the type "theme"
207
	 * @param string $app
208
	 */
209
	private static function enableThemeIfApplicable($app) {
210
		if (self::isType($app, 'theme')) {
211
			/** @var \OCP\Theme\IThemeService $themeService */
212
			$themeService = \OC::$server->query('ThemeService');
213
			$themeService->setAppTheme($app);
214
		}
215
	}
216
217
	/**
218
	 * @internal
219
	 * @param string $app
220
	 * @param string $path
221
	 */
222
	public static function registerAutoloading($app, $path) {
223
		// Register on PSR-4 composer autoloader
224
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
225
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
226
		if (defined('PHPUNIT_RUN')) {
227
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
228
		}
229
230
		// Register on legacy autoloader
231
		\OC::$loader->addValidRoot($path);
232
	}
233
234
	/**
235
	 * Load app.php from the given app
236
	 *
237
	 * @param string $app app name
238
	 */
239
	private static function requireAppFile($app) {
240
		try {
241
			// encapsulated here to avoid variable scope conflicts
242
			require_once $app . '/appinfo/app.php';
243
		} catch (Exception $ex) {
244
			\OC::$server->getLogger()->logException($ex);
245
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
246
			if (!in_array($app, $blacklist)) {
247
				if (!self::isType($app, ['authentication', 'filesystem'])) {
248
					\OC::$server->getLogger()->warning('Could not load app "' . $app . '", it will be disabled', array('app' => 'core'));
249
					self::disable($app);
250
				} else {
251
					\OC::$server->getLogger()->warning('Could not load app "' . $app . '", see exception above', array('app' => 'core'));
252
				}
253
			}
254
			throw $ex;
255
		}
256
	}
257
258
	/**
259
	 * check if an app is of a specific type
260
	 *
261
	 * @param string $app
262
	 * @param string|array $types
263
	 * @return bool
264
	 */
265
	public static function isType($app, $types) {
266
		if (is_string($types)) {
267
			$types = [$types];
268
		}
269
		$appTypes = self::getAppTypes($app);
270
		foreach ($types as $type) {
271
			if (array_search($type, $appTypes) !== false) {
272
				return true;
273
			}
274
		}
275
		return false;
276
	}
277
278
	/**
279
	 * get the types of an app
280
	 *
281
	 * @param string $app
282
	 * @return array
283
	 */
284
	private static function getAppTypes($app) {
285
		//load the cache
286
		if (count(self::$appTypes) == 0) {
287
			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...
288
		}
289
290
		if (isset(self::$appTypes[$app])) {
291
			return explode(',', self::$appTypes[$app]);
292
		} else {
293
			return [];
294
		}
295
	}
296
297
	/**
298
	 * read app types from info.xml and cache them in the database
299
	 */
300
	public static function setAppTypes($app) {
301
		$appData = self::getAppInfo($app);
302
		if(!is_array($appData)) {
303
			return;
304
		}
305
306
		if (isset($appData['types'])) {
307
			$appTypes = implode(',', $appData['types']);
308
		} else {
309
			$appTypes = '';
310
		}
311
312
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
313
	}
314
315
	/**
316
	 * check if app is shipped
317
	 *
318
	 * @param string $appId the id of the app to check
319
	 * @return bool
320
	 *
321
	 * Check if an app that is installed is a shipped app or installed from the appstore.
322
	 */
323
	public static function isShipped($appId) {
324
		return \OC::$server->getAppManager()->isShipped($appId);
325
	}
326
327
	/**
328
	 * get all enabled apps
329
	 */
330
	protected static $enabledAppsCache = [];
331
332
	/**
333
	 * Returns apps enabled for the current user.
334
	 *
335
	 * @param bool $forceRefresh whether to refresh the cache
336
	 * @param bool $all whether to return apps for all users, not only the
337
	 * currently logged in one
338
	 * @return string[]
339
	 */
340
	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...
341
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
342
			return [];
343
		}
344
		// in incognito mode or when logged out, $user will be false,
345
		// which is also the case during an upgrade
346
		$appManager = \OC::$server->getAppManager();
347
		if ($all) {
348
			$user = null;
349
		} else {
350
			$user = \OC::$server->getUserSession()->getUser();
351
		}
352
353
		if (is_null($user)) {
354
			$apps = $appManager->getInstalledApps();
355
		} else {
356
			$apps = $appManager->getEnabledAppsForUser($user);
357
		}
358
		$apps = array_filter($apps, function ($app) {
359
			return $app !== 'files';//we add this manually
360
		});
361
		sort($apps);
362
		array_unshift($apps, 'files');
363
		return $apps;
364
	}
365
366
	/**
367
	 * checks whether or not an app is enabled
368
	 *
369
	 * @param string $app app
370
	 * @return bool
371
	 *
372
	 * This function checks whether or not an app is enabled.
373
	 */
374
	public static function isEnabled($app) {
375
		return \OC::$server->getAppManager()->isEnabledForUser($app);
376
	}
377
378
	/**
379
	 * enables an app
380
	 *
381
	 * @param mixed $app app
382
	 * @param array $groups (optional) when set, only these groups will have access to the app
383
	 * @throws \Exception
384
	 * @return void
385
	 *
386
	 * This function set an app as enabled in appconfig.
387
	 */
388
	public static function enable($app, $groups = null) {
389
		self::$enabledAppsCache = []; // flush
390
391
		// check for required dependencies
392
		$config = \OC::$server->getConfig();
393
		$l = \OC::$server->getL10N('core');
394
		$info = self::getAppInfo($app);
395
		if ($info === null) {
396
			throw new \Exception("$app can't be enabled since it is not installed.");
397
		}
398
399
		self::checkAppDependencies($config, $l, $info);
400
401
		if (!Installer::isInstalled($app)) {
402
			Installer::installShippedApp($app);
403
		}
404
405
		$appManager = \OC::$server->getAppManager();
406
		if (!is_null($groups)) {
407
			$groupManager = \OC::$server->getGroupManager();
408
			$groupsList = [];
409
			foreach ($groups as $group) {
410
				$groupItem = $groupManager->get($group);
411
				if ($groupItem instanceof \OCP\IGroup) {
412
					$groupsList[] = $groupManager->get($group);
413
				}
414
			}
415
			$appManager->enableAppForGroups($app, $groupsList);
416
		} else {
417
			$appManager->enableApp($app);
418
		}
419
	}
420
421
	/**
422
	 * @param string $app
423
	 * @return bool
424
	 */
425
	public static function removeApp($app) {
426
		if (self::isShipped($app)) {
427
			return false;
428
		}
429
430
		return Installer::removeApp($app);
431
	}
432
433
	/**
434
	 * This function set an app as disabled in appconfig.
435
	 *
436
	 * @param string $app app
437
	 * @throws Exception
438
	 */
439
	public static function disable($app) {
440
		// Convert OCS ID to regular application identifier
441
		if(self::getInternalAppIdByOcs($app) !== false) {
442
			$app = self::getInternalAppIdByOcs($app);
443
		}
444
445
		// flush
446
		self::$enabledAppsCache = [];
447
448
		// run uninstall steps
449
		$appData = OC_App::getAppInfo($app);
450
		if (!is_null($appData)) {
451
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
452
		}
453
454
		// emit disable hook - needed anymore ?
455
		\OC_Hook::emit('OC_App', 'pre_disable', ['app' => $app]);
456
457
		// finally disable it
458
		$appManager = \OC::$server->getAppManager();
459
		$appManager->disableApp($app);
460
	}
461
462
	/**
463
	 * Returns the Settings Navigation
464
	 *
465
	 * @return string[]
466
	 *
467
	 * This function returns an array containing all settings pages added. The
468
	 * entries are sorted by the key 'order' ascending.
469
	 */
470
	public static function getSettingsNavigation() {
471
		$l = \OC::$server->getL10N('lib');
472
		$urlGenerator = \OC::$server->getURLGenerator();
473
474
		$settings = [];
475
		// by default, settings only contain the help menu
476
		if (OC_Util::getEditionString() === OC_Util::EDITION_COMMUNITY &&
477
			\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true
478
		) {
479
			$settings = [
480
				[
481
					"id" => "help",
482
					"order" => 1000,
483
					"href" => $urlGenerator->linkToRoute('settings_help'),
484
					"name" => $l->t("Help"),
485
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
486
				]
487
			];
488
		}
489
490
		// if the user is logged-in
491
		if (OC_User::isLoggedIn()) {
492
			// personal menu
493
			$settings[] = [
494
				"id" => "settings",
495
				"order" => 1,
496
				"href" => $urlGenerator->linkToRoute('settings.SettingsPage.getPersonal'),
497
				"name" => $l->t("Settings"),
498
				"icon" => $urlGenerator->imagePath("settings", "admin.svg")
499
			];
500
501
			$hasUserManagementPrivileges = false;
502
			$userObject = \OC::$server->getUserSession()->getUser();
503 View Code Duplication
			if($userObject !== 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...
504
				//Admin and SubAdmins are allowed to access user management
505
				$hasUserManagementPrivileges = \OC::$server->getGroupManager()->isAdmin($userObject->getUID())
506
					|| \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
507
			}
508
			if ($hasUserManagementPrivileges) {
509
				// admin users menu
510
				$settings[] = [
511
					"id" => "core_users",
512
					"order" => 2,
513
					"href" => $urlGenerator->linkToRoute('settings_users'),
514
					"name" => $l->t("Users"),
515
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
516
				];
517
			}
518
519
		}
520
521
		$navigation = self::proceedNavigation($settings);
522
		return $navigation;
523
	}
524
525
	// This is private as well. It simply works, so don't ask for more details
526
	private static function proceedNavigation($list) {
527
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
528
		foreach ($list as &$navEntry) {
529
			if ($navEntry['id'] == $activeApp) {
530
				$navEntry['active'] = true;
531
			} else {
532
				$navEntry['active'] = false;
533
			}
534
		}
535
		unset($navEntry);
536
537
		usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}'));
538
539
		return $list;
540
	}
541
542
	/**
543
	 * Get the path where to install apps
544
	 *
545
	 * @return string|false
546
	 */
547
	public static function getInstallPath() {
548
		foreach (OC::$APPSROOTS as $dir) {
549
			if (isset($dir['writable']) && $dir['writable'] === true) {
550
				return $dir['path'];
551
			}
552
		}
553
554
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
555
		return null;
556
	}
557
558
	/**
559
	 * Get the directory for the given app.
560
	 * If the app exists in multiple directories, the most recent version is taken.
561
	 * (false if not found)
562
	 *
563
	 * @param string $appId
564
	 * @return string|false
565
	 */
566
	public static function getAppPath($appId) {
567
		return \OC::$server->getAppManager()->getAppPath($appId);
568
	}
569
570
	/**
571
	 * Get the web path for the given app.
572
	 * If the app exists in multiple directories, the most recent version is taken.
573
	 * (false if not found)
574
	 *
575
	 * @param string $appId
576
	 * @return string|false
577
	 */
578
	public static function getAppWebPath($appId) {
579
		return \OC::$server->getAppManager()->getAppWebPath($appId);
580
	}
581
582
	/**
583
	 * check if an app's directory is writable
584
	 *
585
	 * @param string $appId
586
	 * @return bool
587
	 */
588
	public static function isAppDirWritable($appId) {
589
		$path = self::getAppPath($appId);
590
		return ($path !== false) ? is_writable($path) : false;
591
	}
592
593
	/**
594
	 * get the last version of the app from appinfo/info.xml
595
	 *
596
	 * @param string $appId
597
	 * @return string
598
	 */
599
	public static function getAppVersion($appId) {
600
		if (!isset(self::$appVersion[$appId])) {
601
			$file = self::getAppPath($appId);
602
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
603
		}
604
		return self::$appVersion[$appId];
605
	}
606
607
	/**
608
	 * get app's version based on it's path
609
	 *
610
	 * @param string $path
611
	 * @return string
612
	 */
613
	public static function getAppVersionByPath($path) {
614
		$infoFile = $path . '/appinfo/info.xml';
615
		$appData = self::getAppInfo($infoFile, true);
616
		return isset($appData['version']) ? $appData['version'] : '';
617
	}
618
619
	/**
620
	 * Read all app metadata from the info.xml file
621
	 *
622
	 * @param string $appId id of the app or the path of the info.xml file
623
	 * @param boolean $path (optional)
624
	 * @return array|null
625
	 * @note all data is read from info.xml, not just pre-defined fields
626
	 */
627
	public static function getAppInfo($appId, $path = false) {
628
		if ($path) {
629
			$file = $appId;
630
		} else {
631
			if (isset(self::$appInfo[$appId])) {
632
				return self::$appInfo[$appId];
633
			}
634
			$appPath = self::getAppPath($appId);
635
			if($appPath === false) {
636
				return null;
637
			}
638
			$file = $appPath . '/appinfo/info.xml';
639
		}
640
641
		$parser = new InfoParser();
642
		$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 656 which is incompatible with the return type documented by OC_App::getAppInfo of type array|null.
Loading history...
643
644
		if (is_array($data)) {
645
			$data = OC_App::parseAppInfo($data);
646
		}
647
		if(isset($data['ocsid'])) {
648
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
649
			if($storedId !== '' && $storedId !== $data['ocsid']) {
650
				$data['ocsid'] = $storedId;
651
			}
652
		}
653
654
		self::$appInfo[$appId] = $data;
655
656
		return $data;
657
	}
658
659
	/**
660
	 * Returns the navigation
661
	 *
662
	 * @return array
663
	 *
664
	 * This function returns an array containing all entries added. The
665
	 * entries are sorted by the key 'order' ascending. Additional to the keys
666
	 * given for each app the following keys exist:
667
	 *   - active: boolean, signals if the user is on this navigation entry
668
	 */
669
	public static function getNavigation() {
670
		$entries = OC::$server->getNavigationManager()->getAll();
671
		$navigation = self::proceedNavigation($entries);
672
		return $navigation;
673
	}
674
675
	/**
676
	 * get the id of loaded app
677
	 *
678
	 * @return string
679
	 */
680
	public static function getCurrentApp() {
681
		$request = \OC::$server->getRequest();
682
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
683
		$topFolder = substr($script, 0, strpos($script, '/'));
684
		if (empty($topFolder)) {
685
			$path_info = $request->getPathInfo();
686
			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...
687
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
688
			}
689
		}
690
		if ($topFolder == 'apps') {
691
			$length = strlen($topFolder);
692
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
693
		} else {
694
			return $topFolder;
695
		}
696
	}
697
698
	/**
699
	 * @param string $type
700
	 * @return array
701
	 */
702
	public static function getForms($type) {
703
		$forms = [];
704
		switch ($type) {
705
			case 'admin':
706
				$source = self::$adminForms;
707
				break;
708
			case 'personal':
709
				$source = self::$personalForms;
710
				break;
711
			default:
712
				return [];
713
		}
714
		foreach ($source as $form) {
715
			$page = include $form['page'];
716
			$forms[] = [
717
				'appId' => $form['appId'],
718
				'page' => $page,
719
			];
720
		}
721
		return $forms;
722
	}
723
724
	/**
725
	 * register an admin form to be shown
726
	 *
727
	 * @param string $app
728
	 * @param string $page
729
	 * @deprecated Register settings panels in info.xml
730
	 */
731
	public static function registerAdmin($app, $page) {
732
		self::$adminForms[] = [
733
			'appId' => $app,
734
			'page' => $app . '/' . $page . '.php',
735
		];
736
	}
737
738
	/**
739
	 * register a personal form to be shown
740
	 * @param string $app
741
	 * @param string $page
742
	 * @deprecated Register settings panels in info.xml
743
	 */
744
	public static function registerPersonal($app, $page) {
745
		self::$personalForms[] = [
746
			'appId' => $app,
747
			'page' => $app . '/' . $page . '.php',
748
		];
749
	}
750
751
	/**
752
	 * @param array $entry
753
	 */
754
	public static function registerLogIn(array $entry) {
755
		self::$altLogin[] = $entry;
756
	}
757
758
	/**
759
	 * @return array
760
	 */
761
	public static function getAlternativeLogIns() {
762
		return self::$altLogin;
763
	}
764
765
	/**
766
	 * get a list of all apps in the apps folder
767
	 *
768
	 * @return array an array of app names (string IDs)
769
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
770
	 */
771
	public static function getAllApps() {
772
773
		$apps = [];
774
775
		foreach (OC::$APPSROOTS as $apps_dir) {
776
			if (!is_readable($apps_dir['path'])) {
777
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
778
				continue;
779
			}
780
			$dh = opendir($apps_dir['path']);
781
782
			if (is_resource($dh)) {
783
				while (($file = readdir($dh)) !== false) {
784
785
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
786
787
						$apps[] = $file;
788
					}
789
				}
790
			}
791
		}
792
793
		return $apps;
794
	}
795
796
	/**
797
	 * List all apps, this is used in apps.php
798
	 *
799
	 * @param bool $onlyLocal
0 ignored issues
show
Bug introduced by
There is no parameter named $onlyLocal. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
800
	 * @param bool $includeUpdateInfo Should we check whether there is an update
0 ignored issues
show
Bug introduced by
There is no parameter named $includeUpdateInfo. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
801
	 *                                in the app store?
802
	 * @return array
803
	 */
804
	public static function listAllApps() {
805
		$installedApps = OC_App::getAllApps();
806
807
		//TODO which apps do we want to blacklist and how do we integrate
808
		// blacklisting with the multi apps folder feature?
809
810
		//we don't want to show configuration for these
811
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
812
		$appList = [];
813
		$urlGenerator = \OC::$server->getURLGenerator();
814
815
		foreach ($installedApps as $app) {
816
			if (array_search($app, $blacklist) === false) {
817
818
				$info = OC_App::getAppInfo($app);
819
				if (!is_array($info)) {
820
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
821
					continue;
822
				}
823
824
				if (!isset($info['name'])) {
825
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
826
					continue;
827
				}
828
829
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
830
				$info['groups'] = null;
831
				if ($enabled === 'yes') {
832
					$active = true;
833
				} else if ($enabled === 'no') {
834
					$active = false;
835
				} else {
836
					$active = true;
837
					$info['groups'] = $enabled;
838
				}
839
840
				$info['active'] = $active;
841
842
				if (self::isShipped($app)) {
843
					$info['internal'] = true;
844
					$info['level'] = self::officialApp;
845
					$info['removable'] = false;
846
				} else {
847
					$result = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature($app, '', true);
848
					if (empty($result)) {
849
						$info['internal'] = false;
850
						$info['level'] = self::approvedApp;
851
						$info['removable'] = false;
852
					}
853
854
					$info['internal'] = false;
855
					$info['removable'] = true;
856
				}
857
858
				$appPath = self::getAppPath($app);
859
				if($appPath !== false) {
860
					$appIcon = $appPath . '/img/' . $app . '.svg';
861
					if (file_exists($appIcon)) {
862
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
863
						$info['previewAsIcon'] = true;
864
					} else {
865
						$appIcon = $appPath . '/img/app.svg';
866
						if (file_exists($appIcon)) {
867
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
868
							$info['previewAsIcon'] = true;
869
						}
870
					}
871
				}
872
				// fix documentation
873
				if (isset($info['documentation']) && is_array($info['documentation'])) {
874
					foreach ($info['documentation'] as $key => $url) {
875
						// If it is not an absolute URL we assume it is a key
876
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
877
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
878
							$url = $urlGenerator->linkToDocs($url);
879
						}
880
881
						$info['documentation'][$key] = $url;
882
					}
883
				}
884
885
				$info['version'] = OC_App::getAppVersion($app);
886
				$appList[] = $info;
887
			}
888
		}
889
890
		return $appList;
891
	}
892
893
	/**
894
	 * Returns the internal app ID or false
895
	 * @param string $ocsID
896
	 * @return string|false
897
	 */
898
	public static function getInternalAppIdByOcs($ocsID) {
899
		if(is_numeric($ocsID)) {
900
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
901
			if(array_search($ocsID, $idArray)) {
902
				return array_search($ocsID, $idArray);
903
			}
904
		}
905
		return false;
906
	}
907
908
	public static function shouldUpgrade($app) {
909
		$versions = self::getAppVersions();
910
		$currentVersion = OC_App::getAppVersion($app);
911
		if ($currentVersion && isset($versions[$app])) {
912
			$installedVersion = $versions[$app];
913
			if (!version_compare($currentVersion, $installedVersion, '=')) {
914
				return true;
915
			}
916
		}
917
		return false;
918
	}
919
920
	/**
921
	 * Adjust the number of version parts of $version1 to match
922
	 * the number of version parts of $version2.
923
	 *
924
	 * @param string $version1 version to adjust
925
	 * @param string $version2 version to take the number of parts from
926
	 * @return string shortened $version1
927
	 */
928
	private static function adjustVersionParts($version1, $version2) {
929
		$version1 = explode('.', $version1);
930
		$version2 = explode('.', $version2);
931
		// reduce $version1 to match the number of parts in $version2
932
		while (count($version1) > count($version2)) {
933
			array_pop($version1);
934
		}
935
		// if $version1 does not have enough parts, add some
936
		while (count($version1) < count($version2)) {
937
			$version1[] = '0';
938
		}
939
		return implode('.', $version1);
940
	}
941
942
	/**
943
	 * Check whether the current ownCloud version matches the given
944
	 * application's version requirements.
945
	 *
946
	 * The comparison is made based on the number of parts that the
947
	 * app info version has. For example for ownCloud 6.0.3 if the
948
	 * app info version is expecting version 6.0, the comparison is
949
	 * made on the first two parts of the ownCloud version.
950
	 * This means that it's possible to specify "requiremin" => 6
951
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
952
	 *
953
	 * @param string|array $ocVersion ownCloud version to check against
954
	 * @param array $appInfo app info (from xml)
955
	 *
956
	 * @return boolean true if compatible, otherwise false
957
	 */
958
	public static function isAppCompatible($ocVersion, $appInfo) {
959
		$requireMin = '';
960
		$requireMax = '';
961
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
962
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
963
		} else if (isset($appInfo['requiremin'])) {
964
			$requireMin = $appInfo['requiremin'];
965
		} else if (isset($appInfo['require'])) {
966
			$requireMin = $appInfo['require'];
967
		}
968
969
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
970
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
971
		} else if (isset($appInfo['requiremax'])) {
972
			$requireMax = $appInfo['requiremax'];
973
		}
974
975
		if (is_array($ocVersion)) {
976
			$ocVersion = implode('.', $ocVersion);
977
		}
978
979 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...
980
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
981
		) {
982
983
			return false;
984
		}
985
986 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...
987
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
988
		) {
989
			return false;
990
		}
991
992
		return true;
993
	}
994
995
	/**
996
	 * get the installed version of all apps
997
	 */
998
	public static function getAppVersions() {
999
		static $versions;
1000
1001
		if(!$versions) {
1002
			$appConfig = \OC::$server->getAppConfig();
1003
			$versions = $appConfig->getValues(false, 'installed_version');
1004
		}
1005
		return $versions;
1006
	}
1007
1008
	/**
1009
	 * update the database for the app and call the update script
1010
	 *
1011
	 * @param string $appId
1012
	 * @return bool
1013
	 */
1014
	public static function updateApp($appId) {
1015
		$appPath = self::getAppPath($appId);
1016
		if($appPath === false) {
1017
			return false;
1018
		}
1019
		$appData = self::getAppInfo($appId);
1020
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1021
		if (isset($appData['use-migrations']) && $appData['use-migrations'] === 'true') {
1022
			$ms = new \OC\DB\MigrationService($appId, \OC::$server->getDatabaseConnection());
1023
			$ms->migrate();
1024
		} else {
1025
			if (file_exists($appPath . '/appinfo/database.xml')) {
1026
				OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1027
			}
1028
		}
1029
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1030
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1031
		self::clearAppCache($appId);
1032
		// run upgrade code
1033
		if (file_exists($appPath . '/appinfo/update.php')) {
1034
			self::loadApp($appId, false);
1035
			include $appPath . '/appinfo/update.php';
1036
		}
1037
		self::setupBackgroundJobs($appData['background-jobs']);
1038
1039
		//set remote/public handlers
1040
		if (array_key_exists('ocsid', $appData)) {
1041
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1042 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...
1043
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1044
		}
1045 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...
1046
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1047
		}
1048
		foreach ($appData['public'] as $name => $path) {
1049
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1050
		}
1051
1052
		self::setAppTypes($appId);
1053
1054
		$version = \OC_App::getAppVersion($appId);
1055
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
1056
1057
		return true;
1058
	}
1059
1060
	/**
1061
	 * @param string $appId
1062
	 * @param string[] $steps
1063
	 * @throws \OC\NeedsUpdateException
1064
	 */
1065
	public static function executeRepairSteps($appId, array $steps) {
1066
		if (empty($steps)) {
1067
			return;
1068
		}
1069
		// load the app
1070
		self::loadApp($appId, false);
1071
1072
		$dispatcher = OC::$server->getEventDispatcher();
1073
1074
		// load the steps
1075
		$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...
1076
		foreach ($steps as $step) {
1077
			try {
1078
				$r->addStep($step);
1079
			} catch (Exception $ex) {
1080
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1081
				\OC::$server->getLogger()->logException($ex);
1082
			}
1083
		}
1084
		// run the steps
1085
		$r->run();
1086
	}
1087
1088
	public static function setupBackgroundJobs(array $jobs) {
1089
		$queue = \OC::$server->getJobList();
1090
		foreach ($jobs as $job) {
1091
			$queue->add($job);
1092
		}
1093
	}
1094
1095
	/**
1096
	 * @param string $appId
1097
	 * @param string[] $steps
1098
	 */
1099
	private static function setupLiveMigrations($appId, array $steps) {
1100
		$queue = \OC::$server->getJobList();
1101
		foreach ($steps as $step) {
1102
			$queue->add('OC\Migration\BackgroundRepair', [
1103
				'app' => $appId,
1104
				'step' => $step]);
1105
		}
1106
	}
1107
1108
	/**
1109
	 * @param string $appId
1110
	 * @return \OC\Files\View|false
1111
	 */
1112
	public static function getStorage($appId) {
1113
		if (OC_App::isEnabled($appId)) { //sanity check
1114
			if (OC_User::isLoggedIn()) {
1115
				$view = new \OC\Files\View('/' . OC_User::getUser());
1116
				if (!$view->file_exists($appId)) {
1117
					$view->mkdir($appId);
1118
				}
1119
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1120
			} else {
1121
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1122
				return false;
1123
			}
1124
		} else {
1125
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1126
			return false;
1127
		}
1128
	}
1129
1130
	/**
1131
	 * parses the app data array and enhanced the 'description' value
1132
	 *
1133
	 * @param array $data the app data
1134
	 * @return array improved app data
1135
	 */
1136
	public static function parseAppInfo(array $data) {
1137
1138
		// just modify the description if it is available
1139
		// otherwise this will create a $data element with an empty 'description'
1140
		if (isset($data['description'])) {
1141
			if (is_string($data['description'])) {
1142
				// sometimes the description contains line breaks and they are then also
1143
				// shown in this way in the app management which isn't wanted as HTML
1144
				// manages line breaks itself
1145
1146
				// first of all we split on empty lines
1147
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1148
1149
				$result = [];
1150
				foreach ($paragraphs as $value) {
1151
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1152
					// with a single space - also trims whitespace
1153
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1154
				}
1155
1156
				// join the single paragraphs with a empty line in between
1157
				$data['description'] = implode("\n\n", $result);
1158
1159
			} else {
1160
				$data['description'] = '';
1161
			}
1162
		}
1163
1164
		return $data;
1165
	}
1166
1167
	/**
1168
	 * @param OCP\IConfig $config
1169
	 * @param OCP\IL10N $l
1170
	 * @param array $info
1171
	 * @throws Exception
1172
	 */
1173
	protected static function checkAppDependencies($config, $l, $info) {
1174
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1175
		$missing = $dependencyAnalyzer->analyze($info);
1176
		if (!empty($missing)) {
1177
			$missingMsg = join(PHP_EOL, $missing);
1178
			throw new \Exception(
1179
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1180
					[$info['name'], $missingMsg]
1181
				)
1182
			);
1183
		}
1184
	}
1185
1186
	/**
1187
	 * @param $appId
1188
	 */
1189
	public static function clearAppCache($appId) {
1190
		unset(self::$appVersion[$appId]);
1191
		unset(self::$appInfo[$appId]);
1192
	}
1193
}
1194