Completed
Push — master ( 154166...57278e )
by Thomas
29:37 queued 16:14
created

OC_App::getNavigation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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) 2018, 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
		if (\is_array($types)) {
133
			self::$loadedTypes = \array_merge(self::$loadedTypes, $types);
134
		}
135
136
		\OC_Hook::emit('OC_App', 'loadedApps');
137
		return true;
138
	}
139
140
	/**
141
	 * load a single app
142
	 *
143
	 * @param string $app
144
	 * @param bool $checkUpgrade whether an upgrade check should be done
145
	 * @throws \OC\NeedsUpdateException
146
	 */
147
	public static function loadApp($app, $checkUpgrade = true) {
148
		self::$loadedApps[] = $app;
149
		$appPath = self::getAppPath($app);
150
		if($appPath === false) {
151
			return;
152
		}
153
154
		// in case someone calls loadApp() directly
155
		self::registerAutoloading($app, $appPath);
156
157
		self::enableThemeIfApplicable($app);
158
159
		if (\is_file($appPath . '/appinfo/app.php')) {
160
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
161
			if ($checkUpgrade and self::shouldUpgrade($app)) {
162
				throw new \OC\NeedsUpdateException();
163
			}
164
			self::requireAppFile($app);
165
			if (self::isType($app, ['authentication'])) {
166
				// since authentication apps affect the "is app enabled for group" check,
167
				// the enabled apps cache needs to be cleared to make sure that the
168
				// next time getEnableApps() is called it will also include apps that were
169
				// enabled for groups
170
				self::$enabledAppsCache = [];
171
			}
172
			\OC::$server->getEventLogger()->end('load_app_' . $app);
173
		}
174
	}
175
176
	/**
177
	 * Enables the app as a theme if it has the type "theme"
178
	 * @param string $app
179
	 */
180
	private static function enableThemeIfApplicable($app) {
181
		if (self::isType($app, 'theme')) {
182
			/** @var \OCP\Theme\IThemeService $themeService */
183
			$themeService = \OC::$server->query('ThemeService');
184
			$themeService->setAppTheme($app);
185
		}
186
	}
187
188
	/**
189
	 * @internal
190
	 * @param string $app
191
	 * @param string $path
192
	 */
193
	public static function registerAutoloading($app, $path) {
194
		// Register on PSR-4 composer autoloader
195
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
196
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
197
		if (\defined('PHPUNIT_RUN')) {
198
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
199
		}
200
201
		// Register on legacy autoloader
202
		\OC::$loader->addValidRoot($path);
203
	}
204
205
	/**
206
	 * Load app.php from the given app
207
	 *
208
	 * @param string $app app name
209
	 */
210
	private static function requireAppFile($app) {
211
		try {
212
			// encapsulated here to avoid variable scope conflicts
213
			require_once $app . '/appinfo/app.php';
214
		} catch (Exception $ex) {
215
			\OC::$server->getLogger()->logException($ex);
216
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
217
			if (!\in_array($app, $blacklist)) {
218
				if (!self::isType($app, ['authentication', 'filesystem'])) {
219
					\OC::$server->getLogger()->warning('Could not load app "' . $app . '", it will be disabled', ['app' => 'core']);
220
					self::disable($app);
221
				} else {
222
					\OC::$server->getLogger()->warning('Could not load app "' . $app . '", see exception above', ['app' => 'core']);
223
				}
224
			}
225
			throw $ex;
226
		}
227
	}
228
229
	/**
230
	 * check if an app is of a specific type
231
	 *
232
	 * @param string $app
233
	 * @param string|array $types
234
	 * @return bool
235
	 */
236
	public static function isType($app, $types) {
237
		if (\is_string($types)) {
238
			$types = [$types];
239
		}
240
		$appTypes = self::getAppTypes($app);
241
		foreach ($types as $type) {
242
			if (\array_search($type, $appTypes) !== false) {
243
				return true;
244
			}
245
		}
246
		return false;
247
	}
248
249
	/**
250
	 * get the types of an app
251
	 *
252
	 * @param string $app
253
	 * @return array
254
	 */
255
	private static function getAppTypes($app) {
256
		//load the cache
257
		if (\count(self::$appTypes) == 0) {
258
			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...
259
		}
260
261
		if (isset(self::$appTypes[$app])) {
262
			return \explode(',', self::$appTypes[$app]);
263
		} else {
264
			return [];
265
		}
266
	}
267
268
	/**
269
	 * read app types from info.xml and cache them in the database
270
	 */
271
	public static function setAppTypes($app) {
272
		$appData = self::getAppInfo($app);
273
		if(!\is_array($appData)) {
274
			return;
275
		}
276
277
		if (isset($appData['types'])) {
278
			$appTypes = \implode(',', $appData['types']);
279
		} else {
280
			$appTypes = '';
281
		}
282
283
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
284
	}
285
286
	/**
287
	 * check if app is shipped
288
	 *
289
	 * @param string $appId the id of the app to check
290
	 * @return bool
291
	 *
292
	 * Check if an app that is installed is a shipped app or installed from the appstore.
293
	 */
294
	public static function isShipped($appId) {
295
		return \OC::$server->getAppManager()->isShipped($appId);
296
	}
297
298
	/**
299
	 * get all enabled apps
300
	 */
301
	protected static $enabledAppsCache = [];
302
303
	/**
304
	 * Returns apps enabled for the current user.
305
	 *
306
	 * @param bool $forceRefresh whether to refresh the cache
307
	 * @param bool $all whether to return apps for all users, not only the
308
	 * currently logged in one
309
	 * @return string[]
310
	 */
311
	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...
312
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
313
			return [];
314
		}
315
		// in incognito mode or when logged out, $user will be false,
316
		// which is also the case during an upgrade
317
		$appManager = \OC::$server->getAppManager();
318
		if ($all) {
319
			$user = null;
320
		} else {
321
			$user = \OC::$server->getUserSession()->getUser();
322
		}
323
324
		if (\is_null($user)) {
325
			$apps = $appManager->getInstalledApps();
326
		} else {
327
			$apps = $appManager->getEnabledAppsForUser($user);
328
		}
329
		$apps = \array_filter($apps, function ($app) {
330
			return $app !== 'files';//we add this manually
331
		});
332
		\sort($apps);
333
		\array_unshift($apps, 'files');
334
		return $apps;
335
	}
336
337
	/**
338
	 * checks whether or not an app is enabled
339
	 *
340
	 * @param string $app app
341
	 * @return bool
342
	 *
343
	 * This function checks whether or not an app is enabled.
344
	 */
345
	public static function isEnabled($app) {
346
		return \OC::$server->getAppManager()->isEnabledForUser($app);
347
	}
348
349
	/**
350
	 * enables an app
351
	 *
352
	 * @param mixed $app app
353
	 * @param array $groups (optional) when set, only these groups will have access to the app
354
	 * @throws \Exception
355
	 * @return void
356
	 *
357
	 * This function set an app as enabled in appconfig.
358
	 */
359
	public static function enable($app, $groups = null) {
360
		self::$enabledAppsCache = []; // flush
361
362
		// check for required dependencies
363
		$config = \OC::$server->getConfig();
364
		$l = \OC::$server->getL10N('core');
365
		$info = self::getAppInfo($app);
366
		if ($info === null) {
367
			throw new \Exception("$app can't be enabled since it is not installed.");
368
		}
369
370
		self::checkAppDependencies($config, $l, $info);
371
372
		if (!Installer::isInstalled($app)) {
373
			Installer::installShippedApp($app);
374
		}
375
376
		$appManager = \OC::$server->getAppManager();
377
		if (!\is_null($groups)) {
378
			$groupManager = \OC::$server->getGroupManager();
379
			$groupsList = [];
380
			foreach ($groups as $group) {
381
				$groupItem = $groupManager->get($group);
382
				if ($groupItem instanceof \OCP\IGroup) {
383
					$groupsList[] = $groupManager->get($group);
384
				}
385
			}
386
			$appManager->enableAppForGroups($app, $groupsList);
387
		} else {
388
			$appManager->enableApp($app);
389
		}
390
	}
391
392
	/**
393
	 * @param string $app
394
	 * @return bool
395
	 */
396
	public static function removeApp($app) {
397
		if (self::isShipped($app)) {
398
			return false;
399
		}
400
401
		return Installer::removeApp($app);
402
	}
403
404
	/**
405
	 * This function set an app as disabled in appconfig.
406
	 *
407
	 * @param string $app app
408
	 * @throws Exception
409
	 */
410
	public static function disable($app) {
411
		// Convert OCS ID to regular application identifier
412
		if(self::getInternalAppIdByOcs($app) !== false) {
413
			$app = self::getInternalAppIdByOcs($app);
414
		}
415
416
		// flush
417
		self::$enabledAppsCache = [];
418
419
		// run uninstall steps
420
		$appData = OC_App::getAppInfo($app);
421
		if (!\is_null($appData)) {
422
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
423
		}
424
425
		// emit disable hook - needed anymore ?
426
		\OC_Hook::emit('OC_App', 'pre_disable', ['app' => $app]);
427
428
		// finally disable it
429
		$appManager = \OC::$server->getAppManager();
430
		$appManager->disableApp($app);
431
	}
432
433
	/**
434
	 * Returns the Settings Navigation
435
	 *
436
	 * @return string[]
437
	 *
438
	 * This function returns an array containing all settings pages added. The
439
	 * entries are sorted by the key 'order' ascending.
440
	 */
441
	public static function getSettingsNavigation() {
442
		$l = \OC::$server->getL10N('lib');
443
		$urlGenerator = \OC::$server->getURLGenerator();
444
445
		$settings = [];
446
		// by default, settings only contain the help menu
447
		if (OC_Util::getEditionString() === OC_Util::EDITION_COMMUNITY &&
448
			\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true
449
		) {
450
			$settings = [
451
				[
452
					"id" => "help",
453
					"order" => 1000,
454
					"href" => $urlGenerator->linkToRoute('settings_help'),
455
					"name" => $l->t("Help"),
456
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
457
				]
458
			];
459
		}
460
461
		// if the user is logged-in
462
		if (OC_User::isLoggedIn()) {
463
			// personal menu
464
			$settings[] = [
465
				"id" => "settings",
466
				"order" => 1,
467
				"href" => $urlGenerator->linkToRoute('settings.SettingsPage.getPersonal'),
468
				"name" => $l->t("Settings"),
469
				"icon" => $urlGenerator->imagePath("settings", "admin.svg")
470
			];
471
		}
472
473
		return self::proceedNavigation($settings);
474
	}
475
476
	// This is private as well. It simply works, so don't ask for more details
477
	private static function proceedNavigation($list) {
478
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
479
		foreach ($list as &$navEntry) {
480
			if ($navEntry['id'] == $activeApp) {
481
				$navEntry['active'] = true;
482
			} else {
483
				$navEntry['active'] = false;
484
			}
485
		}
486
		unset($navEntry);
487
488
		\usort($list, function($a, $b) {
489
			if ($a["order"] == $b["order"]) {
490
				return 0;
491
			}
492
493
			if($a["order"] < $b["order"]) {
494
				return -1;
495
			}
496
497
			return 1;
498
		});
499
500
		return $list;
501
	}
502
503
	/**
504
	 * Get the path where to install apps
505
	 *
506
	 * @return string|null
507
	 */
508
	public static function getInstallPath() {
509
		foreach (OC::$APPSROOTS as $dir) {
510
			if (isset($dir['writable']) && $dir['writable'] === true) {
511
				return $dir['path'];
512
			}
513
		}
514
515
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
516
		return null;
517
	}
518
519
	/**
520
	 * Get the directory for the given app.
521
	 * If the app exists in multiple directories, the most recent version is taken.
522
	 * (false if not found)
523
	 *
524
	 * @param string $appId
525
	 * @return string|false
526
	 */
527
	public static function getAppPath($appId) {
528
		return \OC::$server->getAppManager()->getAppPath($appId);
529
	}
530
531
	/**
532
	 * Get the web path for the given app.
533
	 * If the app exists in multiple directories, the most recent version is taken.
534
	 * (false if not found)
535
	 *
536
	 * @param string $appId
537
	 * @return string|false
538
	 */
539
	public static function getAppWebPath($appId) {
540
		return \OC::$server->getAppManager()->getAppWebPath($appId);
541
	}
542
543
	/**
544
	 * check if an app's directory is writable
545
	 *
546
	 * @param string $appId
547
	 * @return bool
548
	 */
549
	public static function isAppDirWritable($appId) {
550
		$path = self::getAppPath($appId);
551
		return ($path !== false) ? \is_writable($path) : false;
552
	}
553
554
	/**
555
	 * get the last version of the app from appinfo/info.xml
556
	 *
557
	 * @param string $appId
558
	 * @return string
559
	 */
560
	public static function getAppVersion($appId) {
561
		if (!isset(self::$appVersion[$appId])) {
562
			$file = self::getAppPath($appId);
563
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
564
		}
565
		return self::$appVersion[$appId];
566
	}
567
568
	/**
569
	 * get app's version based on it's path
570
	 *
571
	 * @param string $path
572
	 * @return string
573
	 */
574
	public static function getAppVersionByPath($path) {
575
		$infoFile = $path . '/appinfo/info.xml';
576
		$appData = self::getAppInfo($infoFile, true);
577
		return isset($appData['version']) ? $appData['version'] : '';
578
	}
579
580
	/**
581
	 * Read all app metadata from the info.xml file
582
	 *
583
	 * @param string $appId id of the app or the path of the info.xml file
584
	 * @param boolean $path (optional)
585
	 * @return array|null
586
	 * @note all data is read from info.xml, not just pre-defined fields
587
	 */
588
	public static function getAppInfo($appId, $path = false) {
589
		if ($path) {
590
			$file = $appId;
591
		} else {
592
			if (isset(self::$appInfo[$appId])) {
593
				return self::$appInfo[$appId];
594
			}
595
			$appPath = self::getAppPath($appId);
596
			if($appPath === false) {
597
				return null;
598
			}
599
			$file = $appPath . '/appinfo/info.xml';
600
		}
601
602
		$parser = new InfoParser();
603
		try {
604
			$data = $parser->parse($file);
605
		} catch (\Exception $e) {
606
			\OC::$server->getLogger()->logException($e);
607
			throw $e;
608
		}
609
610
		if (\is_array($data)) {
611
			$data = OC_App::parseAppInfo($data);
612
		}
613
		if(isset($data['ocsid'])) {
614
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
615
			if($storedId !== '' && $storedId !== $data['ocsid']) {
616
				$data['ocsid'] = $storedId;
617
			}
618
		}
619
620
		self::$appInfo[$appId] = $data;
621
622
		return $data;
623
	}
624
625
	/**
626
	 * Returns the navigation
627
	 *
628
	 * @return array
629
	 *
630
	 * This function returns an array containing all entries added. The
631
	 * entries are sorted by the key 'order' ascending. Additional to the keys
632
	 * given for each app the following keys exist:
633
	 *   - active: boolean, signals if the user is on this navigation entry
634
	 */
635
	public static function getNavigation() {
636
		$entries = OC::$server->getNavigationManager()->getAll();
637
		return self::proceedNavigation($entries);
638
	}
639
640
	/**
641
	 * get the id of loaded app
642
	 *
643
	 * @return string
644
	 */
645
	public static function getCurrentApp() {
646
		$request = \OC::$server->getRequest();
647
		$script = \substr($request->getScriptName(), \strlen(OC::$WEBROOT) + 1);
648
		$topFolder = \substr($script, 0, \strpos($script, '/'));
649
		if (empty($topFolder)) {
650
			$path_info = $request->getPathInfo();
651
			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...
652
				$topFolder = \substr($path_info, 1, \strpos($path_info, '/', 1) - 1);
653
			}
654
		}
655
		if ($topFolder == 'apps') {
656
			$length = \strlen($topFolder);
657
			return \substr($script, $length + 1, \strpos($script, '/', $length + 1) - $length - 1);
658
		} else {
659
			return $topFolder;
660
		}
661
	}
662
663
	/**
664
	 * @param string $type
665
	 * @return array
666
	 */
667
	public static function getForms($type) {
668
		$forms = [];
669
		switch ($type) {
670
			case 'admin':
671
				$source = self::$adminForms;
672
				break;
673
			case 'personal':
674
				$source = self::$personalForms;
675
				break;
676
			default:
677
				return [];
678
		}
679
		foreach ($source as $form) {
680
			$page = include $form['page'];
681
			$forms[] = [
682
				'appId' => $form['appId'],
683
				'page' => $page,
684
			];
685
		}
686
		return $forms;
687
	}
688
689
	/**
690
	 * register an admin form to be shown
691
	 *
692
	 * @param string $app
693
	 * @param string $page
694
	 * @deprecated Register settings panels in info.xml
695
	 */
696
	public static function registerAdmin($app, $page) {
697
		self::$adminForms[] = [
698
			'appId' => $app,
699
			'page' => $app . '/' . $page . '.php',
700
		];
701
	}
702
703
	/**
704
	 * register a personal form to be shown
705
	 * @param string $app
706
	 * @param string $page
707
	 * @deprecated Register settings panels in info.xml
708
	 */
709
	public static function registerPersonal($app, $page) {
710
		self::$personalForms[] = [
711
			'appId' => $app,
712
			'page' => $app . '/' . $page . '.php',
713
		];
714
	}
715
716
	/**
717
	 * @param array $entry
718
	 */
719
	public static function registerLogIn(array $entry) {
720
		self::$altLogin[] = $entry;
721
	}
722
723
	/**
724
	 * @return array
725
	 */
726
	public static function getAlternativeLogIns() {
727
		return self::$altLogin;
728
	}
729
730
	/**
731
	 * get a list of all apps in the apps folder
732
	 *
733
	 * @return array an array of app names (string IDs)
734
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
735
	 */
736
	public static function getAllApps() {
737
738
		$apps = [];
739
740
		foreach (OC::$APPSROOTS as $apps_dir) {
741
			if (!\is_readable($apps_dir['path'])) {
742
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
743
				continue;
744
			}
745
			$dh = \opendir($apps_dir['path']);
746
747
			if (\is_resource($dh)) {
748
				while (($file = \readdir($dh)) !== false) {
749
750
					if ($file[0] != '.' and \is_dir($apps_dir['path'] . '/' . $file) and \is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
751
752
						$apps[] = $file;
753
					}
754
				}
755
			}
756
		}
757
758
		return $apps;
759
	}
760
761
	/**
762
	 * List all apps, this is used in apps.php
763
	 *
764
	 * @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...
765
	 * @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...
766
	 *                                in the app store?
767
	 * @return array
768
	 */
769
	public static function listAllApps() {
770
		$installedApps = OC_App::getAllApps();
771
772
		//TODO which apps do we want to blacklist and how do we integrate
773
		// blacklisting with the multi apps folder feature?
774
775
		//we don't want to show configuration for these
776
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
777
		$appList = [];
778
		$urlGenerator = \OC::$server->getURLGenerator();
779
780
		foreach ($installedApps as $app) {
781
			if (\array_search($app, $blacklist) === false) {
782
783
				$info = OC_App::getAppInfo($app);
784
				if (!\is_array($info)) {
785
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
786
					continue;
787
				}
788
789
				if (!isset($info['name'])) {
790
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
791
					continue;
792
				}
793
794
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
795
				$info['groups'] = null;
796
				if ($enabled === 'yes') {
797
					$active = true;
798
				} else if ($enabled === 'no') {
799
					$active = false;
800
				} else {
801
					$active = true;
802
					$info['groups'] = $enabled;
803
				}
804
805
				$info['active'] = $active;
806
807
				if (self::isShipped($app)) {
808
					$info['internal'] = true;
809
					$info['level'] = self::officialApp;
810
					$info['removable'] = false;
811
				} else {
812
					$result = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature($app, '', true);
813
					if (empty($result)) {
814
						$info['internal'] = false;
815
						$info['level'] = self::approvedApp;
816
						$info['removable'] = false;
817
					}
818
819
					$info['internal'] = false;
820
					$info['removable'] = true;
821
				}
822
823
				$appPath = self::getAppPath($app);
824
				if($appPath !== false) {
825
					$appIcon = $appPath . '/img/' . $app . '.svg';
826
					if (\file_exists($appIcon)) {
827
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
828
						$info['previewAsIcon'] = true;
829
					} else {
830
						$appIcon = $appPath . '/img/app.svg';
831
						if (\file_exists($appIcon)) {
832
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
833
							$info['previewAsIcon'] = true;
834
						}
835
					}
836
				}
837
				// fix documentation
838
				if (isset($info['documentation']) && \is_array($info['documentation'])) {
839
					foreach ($info['documentation'] as $key => $url) {
840
						// If it is not an absolute URL we assume it is a key
841
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
842
						if (\stripos($url, 'https://') !== 0 && \stripos($url, 'http://') !== 0) {
843
							$url = $urlGenerator->linkToDocs($url);
844
						}
845
846
						$info['documentation'][$key] = $url;
847
					}
848
				}
849
850
				$info['version'] = OC_App::getAppVersion($app);
851
				$appList[] = $info;
852
			}
853
		}
854
855
		return $appList;
856
	}
857
858
	/**
859
	 * Returns the internal app ID or false
860
	 * @param string $ocsID
861
	 * @return string|false
862
	 */
863
	public static function getInternalAppIdByOcs($ocsID) {
864
		if(\is_numeric($ocsID)) {
865
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
866
			if(\array_search($ocsID, $idArray)) {
867
				return \array_search($ocsID, $idArray);
868
			}
869
		}
870
		return false;
871
	}
872
873
	public static function shouldUpgrade($app) {
874
		$versions = self::getAppVersions();
875
		$currentVersion = OC_App::getAppVersion($app);
876
		if ($currentVersion && isset($versions[$app])) {
877
			$installedVersion = $versions[$app];
878
			if (!\version_compare($currentVersion, $installedVersion, '=')) {
879
				return true;
880
			}
881
		}
882
		return false;
883
	}
884
885
	/**
886
	 * Adjust the number of version parts of $version1 to match
887
	 * the number of version parts of $version2.
888
	 *
889
	 * @param string $version1 version to adjust
890
	 * @param string $version2 version to take the number of parts from
891
	 * @return string shortened $version1
892
	 */
893
	private static function adjustVersionParts($version1, $version2) {
894
		$version1 = \explode('.', $version1);
895
		$version2 = \explode('.', $version2);
896
		// reduce $version1 to match the number of parts in $version2
897
		while (\count($version1) > \count($version2)) {
898
			\array_pop($version1);
899
		}
900
		// if $version1 does not have enough parts, add some
901
		while (\count($version1) < \count($version2)) {
902
			$version1[] = '0';
903
		}
904
		return \implode('.', $version1);
905
	}
906
907
	/**
908
	 * Check whether the current ownCloud version matches the given
909
	 * application's version requirements.
910
	 *
911
	 * The comparison is made based on the number of parts that the
912
	 * app info version has. For example for ownCloud 6.0.3 if the
913
	 * app info version is expecting version 6.0, the comparison is
914
	 * made on the first two parts of the ownCloud version.
915
	 * This means that it's possible to specify "requiremin" => 6
916
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
917
	 *
918
	 * @param string|array $ocVersion ownCloud version to check against
919
	 * @param array $appInfo app info (from xml)
920
	 *
921
	 * @return boolean true if compatible, otherwise false
922
	 */
923
	public static function isAppCompatible($ocVersion, $appInfo) {
924
		$requireMin = '';
925
		$requireMax = '';
926
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
927
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
928
		} else if (isset($appInfo['requiremin'])) {
929
			$requireMin = $appInfo['requiremin'];
930
		} else if (isset($appInfo['require'])) {
931
			$requireMin = $appInfo['require'];
932
		}
933
934
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
935
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
936
		} else if (isset($appInfo['requiremax'])) {
937
			$requireMax = $appInfo['requiremax'];
938
		}
939
940
		if (\is_array($ocVersion)) {
941
			$ocVersion = \implode('.', $ocVersion);
942
		}
943
944 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...
945
			&& \version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
946
		) {
947
948
			return false;
949
		}
950
951 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...
952
			&& \version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
953
		) {
954
			return false;
955
		}
956
957
		return true;
958
	}
959
960
	/**
961
	 * get the installed version of all apps
962
	 */
963
	public static function getAppVersions() {
964
		static $versions;
965
966
		if(!$versions) {
967
			$appConfig = \OC::$server->getAppConfig();
968
			$versions = $appConfig->getValues(false, 'installed_version');
969
		}
970
		return $versions;
971
	}
972
973
	/**
974
	 * update the database for the app and call the update script
975
	 *
976
	 * @param string $appId
977
	 * @return bool
978
	 */
979
	public static function updateApp($appId) {
980
		$appPath = self::getAppPath($appId);
981
		if($appPath === false) {
982
			return false;
983
		}
984
		$appData = self::getAppInfo($appId);
985
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
986
		if (isset($appData['use-migrations']) && $appData['use-migrations'] === 'true') {
987
			$ms = new \OC\DB\MigrationService($appId, \OC::$server->getDatabaseConnection());
988
			$ms->migrate();
989
		} else {
990
			if (\file_exists($appPath . '/appinfo/database.xml')) {
991
				OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
992
			}
993
		}
994
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
995
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
996
		self::clearAppCache($appId);
997
		// run upgrade code
998
		if (\file_exists($appPath . '/appinfo/update.php')) {
999
			self::loadApp($appId, false);
1000
			include $appPath . '/appinfo/update.php';
1001
		}
1002
		self::setupBackgroundJobs($appData['background-jobs']);
1003
1004
		//set remote/public handlers
1005
		if (\array_key_exists('ocsid', $appData)) {
1006
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1007 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...
1008
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1009
		}
1010 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...
1011
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1012
		}
1013
		foreach ($appData['public'] as $name => $path) {
1014
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1015
		}
1016
1017
		self::setAppTypes($appId);
1018
1019
		$version = \OC_App::getAppVersion($appId);
1020
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
1021
1022
		return true;
1023
	}
1024
1025
	/**
1026
	 * @param string $appId
1027
	 * @param string[] $steps
1028
	 * @throws \OC\NeedsUpdateException
1029
	 */
1030
	public static function executeRepairSteps($appId, array $steps) {
1031
		if (empty($steps)) {
1032
			return;
1033
		}
1034
		// load the app
1035
		self::loadApp($appId, false);
1036
1037
		$dispatcher = OC::$server->getEventDispatcher();
1038
1039
		// load the steps
1040
		$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...
1041
		foreach ($steps as $step) {
1042
			try {
1043
				$r->addStep($step);
1044
			} catch (Exception $ex) {
1045
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1046
				\OC::$server->getLogger()->logException($ex);
1047
			}
1048
		}
1049
		// run the steps
1050
		$r->run();
1051
	}
1052
1053
	public static function setupBackgroundJobs(array $jobs) {
1054
		$queue = \OC::$server->getJobList();
1055
		foreach ($jobs as $job) {
1056
			$queue->add($job);
1057
		}
1058
	}
1059
1060
	/**
1061
	 * @param string $appId
1062
	 * @param string[] $steps
1063
	 */
1064
	private static function setupLiveMigrations($appId, array $steps) {
1065
		$queue = \OC::$server->getJobList();
1066
		foreach ($steps as $step) {
1067
			$queue->add('OC\Migration\BackgroundRepair', [
1068
				'app' => $appId,
1069
				'step' => $step]);
1070
		}
1071
	}
1072
1073
	/**
1074
	 * @param string $appId
1075
	 * @return \OC\Files\View|false
1076
	 */
1077
	public static function getStorage($appId) {
1078
		if (OC_App::isEnabled($appId)) { //sanity check
1079
			if (OC_User::isLoggedIn()) {
1080
				$view = new \OC\Files\View('/' . OC_User::getUser());
1081
				if (!$view->file_exists($appId)) {
1082
					$view->mkdir($appId);
1083
				}
1084
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1085
			} else {
1086
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1087
				return false;
1088
			}
1089
		} else {
1090
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1091
			return false;
1092
		}
1093
	}
1094
1095
	/**
1096
	 * parses the app data array and enhanced the 'description' value
1097
	 *
1098
	 * @param array $data the app data
1099
	 * @return array improved app data
1100
	 */
1101
	public static function parseAppInfo(array $data) {
1102
1103
		// just modify the description if it is available
1104
		// otherwise this will create a $data element with an empty 'description'
1105
		if (isset($data['description'])) {
1106
			if (\is_string($data['description'])) {
1107
				// sometimes the description contains line breaks and they are then also
1108
				// shown in this way in the app management which isn't wanted as HTML
1109
				// manages line breaks itself
1110
1111
				// first of all we split on empty lines
1112
				$paragraphs = \preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1113
1114
				$result = [];
1115
				foreach ($paragraphs as $value) {
1116
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1117
					// with a single space - also trims whitespace
1118
					$result[] = \trim(\preg_replace('![[:space:]]+!mu', ' ', $value));
1119
				}
1120
1121
				// join the single paragraphs with a empty line in between
1122
				$data['description'] = \implode("\n\n", $result);
1123
1124
			} else {
1125
				$data['description'] = '';
1126
			}
1127
		}
1128
1129
		return $data;
1130
	}
1131
1132
	/**
1133
	 * @param OCP\IConfig $config
1134
	 * @param OCP\IL10N $l
1135
	 * @param array $info
1136
	 * @throws Exception
1137
	 */
1138
	protected static function checkAppDependencies($config, $l, $info) {
1139
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1140
		$missing = $dependencyAnalyzer->analyze($info);
1141
		if (!empty($missing)) {
1142
			$missingMsg = \join(PHP_EOL, $missing);
1143
			throw new \Exception(
1144
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1145
					[$info['name'], $missingMsg]
1146
				)
1147
			);
1148
		}
1149
	}
1150
1151
	/**
1152
	 * @param $appId
1153
	 */
1154
	public static function clearAppCache($appId) {
1155
		unset(self::$appVersion[$appId], self::$appInfo[$appId]);
1156
		
1157
	}
1158
}
1159