Completed
Pull Request — master (#796)
by Blizzz
09:32
created

OC_App::registerAutoloading()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 2
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Bernhard Posselt <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Borjan Tchakaloff <[email protected]>
10
 * @author Brice Maron <[email protected]>
11
 * @author Christopher Schäpers <[email protected]>
12
 * @author Felix Moeller <[email protected]>
13
 * @author Frank Karlitschek <[email protected]>
14
 * @author Georg Ehrke <[email protected]>
15
 * @author Jakob Sack <[email protected]>
16
 * @author Jan-Christoph Borchardt <[email protected]>
17
 * @author Joas Schilling <[email protected]>
18
 * @author Jörn Friedrich Dreyer <[email protected]>
19
 * @author Kamil Domanski <[email protected]>
20
 * @author Klaas Freitag <[email protected]>
21
 * @author Lukas Reschke <[email protected]>
22
 * @author Markus Goetz <[email protected]>
23
 * @author Morris Jobke <[email protected]>
24
 * @author RealRancor <[email protected]>
25
 * @author Robin Appelman <[email protected]>
26
 * @author Robin McCorkell <[email protected]>
27
 * @author Roeland Jago Douma <[email protected]>
28
 * @author Sam Tuke <[email protected]>
29
 * @author Thomas Müller <[email protected]>
30
 * @author Thomas Tanghus <[email protected]>
31
 * @author Tom Needham <[email protected]>
32
 * @author Vincent Petry <[email protected]>
33
 *
34
 * @license AGPL-3.0
35
 *
36
 * This code is free software: you can redistribute it and/or modify
37
 * it under the terms of the GNU Affero General Public License, version 3,
38
 * as published by the Free Software Foundation.
39
 *
40
 * This program is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43
 * GNU Affero General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Affero General Public License, version 3,
46
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
47
 *
48
 */
49
use OC\App\DependencyAnalyzer;
50
use OC\App\Platform;
51
use OC\Installer;
52
use OC\OCSClient;
53
use OC\Repair;
54
use OCP\App\ManagerEvent;
55
56
/**
57
 * This class manages the apps. It allows them to register and integrate in the
58
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
59
 * upgrading and removing apps.
60
 */
61
class OC_App {
62
	static private $appVersion = [];
63
	static private $adminForms = array();
64
	static private $personalForms = array();
65
	static private $appInfo = array();
66
	static private $appTypes = array();
67
	static private $loadedApps = array();
68
	static private $altLogin = array();
69
	const officialApp = 200;
70
71
	/**
72
	 * clean the appId
73
	 *
74
	 * @param string|boolean $app AppId that needs to be cleaned
75
	 * @return string
76
	 */
77
	public static function cleanAppId($app) {
78
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
79
	}
80
81
	/**
82
	 * Check if an app is loaded
83
	 *
84
	 * @param string $app
85
	 * @return bool
86
	 */
87
	public static function isAppLoaded($app) {
88
		return in_array($app, self::$loadedApps, true);
89
	}
90
91
	/**
92
	 * loads all apps
93
	 *
94
	 * @param string[] | string | null $types
95
	 * @return bool
96
	 *
97
	 * This function walks through the ownCloud directory and loads all apps
98
	 * it can find. A directory contains an app if the file /appinfo/info.xml
99
	 * exists.
100
	 *
101
	 * if $types is set, only apps of those types will be loaded
102
	 */
103
	public static function loadApps($types = null) {
104
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
105
			return false;
106
		}
107
		// Load the enabled apps here
108
		$apps = self::getEnabledApps();
109
110
		// Add each apps' folder as allowed class path
111
		foreach($apps as $app) {
112
			$path = self::getAppPath($app);
113
			if($path !== false) {
114
				self::registerAutoloading($app, $path);
115
			}
116
		}
117
118
		// prevent app.php from printing output
119
		ob_start();
120
		foreach ($apps as $app) {
121
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
122
				self::loadApp($app);
123
			}
124
		}
125
		ob_end_clean();
126
127
		return true;
128
	}
129
130
	/**
131
	 * load a single app
132
	 *
133
	 * @param string $app
134
	 * @param bool $checkUpgrade whether an upgrade check should be done
135
	 * @throws \OC\NeedsUpdateException
136
	 */
137
	public static function loadApp($app, $checkUpgrade = true) {
138
		self::$loadedApps[] = $app;
139
		$appPath = self::getAppPath($app);
140
		if($appPath === false) {
141
			return;
142
		}
143
144
		// in case someone calls loadApp() directly
145
		self::registerAutoloading($app, $appPath);
146
147
		if (is_file($appPath . '/appinfo/app.php')) {
148
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
149
			if ($checkUpgrade and self::shouldUpgrade($app)) {
150
				throw new \OC\NeedsUpdateException();
151
			}
152
			self::requireAppFile($app);
153
			if (self::isType($app, array('authentication'))) {
154
				// since authentication apps affect the "is app enabled for group" check,
155
				// the enabled apps cache needs to be cleared to make sure that the
156
				// next time getEnableApps() is called it will also include apps that were
157
				// enabled for groups
158
				self::$enabledAppsCache = array();
159
			}
160
			\OC::$server->getEventLogger()->end('load_app_' . $app);
161
		}
162
	}
163
164
	/**
165
	 * @internal
166
	 * @param string $app
167
	 * @param string $path
168
	 */
169
	public static function registerAutoloading($app, $path) {
170
		// Register on PSR-4 composer autoloader
171
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
172
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
173
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
174
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
175
		}
176
177
		// Register on legacy autoloader
178
		\OC::$loader->addValidRoot($path);
179
	}
180
181
	/**
182
	 * Load app.php from the given app
183
	 *
184
	 * @param string $app app name
185
	 */
186
	private static function requireAppFile($app) {
187
		try {
188
			// encapsulated here to avoid variable scope conflicts
189
			require_once $app . '/appinfo/app.php';
190
		} 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...
191
			\OC::$server->getLogger()->logException($ex);
192
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
193
			if (!in_array($app, $blacklist)) {
194
				self::disable($app);
195
			}
196
		}
197
	}
198
199
	/**
200
	 * check if an app is of a specific type
201
	 *
202
	 * @param string $app
203
	 * @param string|array $types
204
	 * @return bool
205
	 */
206
	public static function isType($app, $types) {
207
		if (is_string($types)) {
208
			$types = array($types);
209
		}
210
		$appTypes = self::getAppTypes($app);
211
		foreach ($types as $type) {
212
			if (array_search($type, $appTypes) !== false) {
213
				return true;
214
			}
215
		}
216
		return false;
217
	}
218
219
	/**
220
	 * get the types of an app
221
	 *
222
	 * @param string $app
223
	 * @return array
224
	 */
225
	private static function getAppTypes($app) {
226
		//load the cache
227
		if (count(self::$appTypes) == 0) {
228
			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...
229
		}
230
231
		if (isset(self::$appTypes[$app])) {
232
			return explode(',', self::$appTypes[$app]);
233
		} else {
234
			return array();
235
		}
236
	}
237
238
	/**
239
	 * read app types from info.xml and cache them in the database
240
	 */
241
	public static function setAppTypes($app) {
242
		$appData = self::getAppInfo($app);
243
		if(!is_array($appData)) {
244
			return;
245
		}
246
247
		if (isset($appData['types'])) {
248
			$appTypes = implode(',', $appData['types']);
249
		} else {
250
			$appTypes = '';
251
		}
252
253
		\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...
254
	}
255
256
	/**
257
	 * check if app is shipped
258
	 *
259
	 * @param string $appId the id of the app to check
260
	 * @return bool
261
	 *
262
	 * Check if an app that is installed is a shipped app or installed from the appstore.
263
	 */
264
	public static function isShipped($appId) {
265
		return \OC::$server->getAppManager()->isShipped($appId);
266
	}
267
268
	/**
269
	 * get all enabled apps
270
	 */
271
	protected static $enabledAppsCache = array();
272
273
	/**
274
	 * Returns apps enabled for the current user.
275
	 *
276
	 * @param bool $forceRefresh whether to refresh the cache
277
	 * @param bool $all whether to return apps for all users, not only the
278
	 * currently logged in one
279
	 * @return string[]
280
	 */
281
	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...
282
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
283
			return array();
284
		}
285
		// in incognito mode or when logged out, $user will be false,
286
		// which is also the case during an upgrade
287
		$appManager = \OC::$server->getAppManager();
288
		if ($all) {
289
			$user = null;
290
		} else {
291
			$user = \OC::$server->getUserSession()->getUser();
292
		}
293
294
		if (is_null($user)) {
295
			$apps = $appManager->getInstalledApps();
296
		} else {
297
			$apps = $appManager->getEnabledAppsForUser($user);
298
		}
299
		$apps = array_filter($apps, function ($app) {
300
			return $app !== 'files';//we add this manually
301
		});
302
		sort($apps);
303
		array_unshift($apps, 'files');
304
		return $apps;
305
	}
306
307
	/**
308
	 * checks whether or not an app is enabled
309
	 *
310
	 * @param string $app app
311
	 * @return bool
312
	 *
313
	 * This function checks whether or not an app is enabled.
314
	 */
315
	public static function isEnabled($app) {
316
		return \OC::$server->getAppManager()->isEnabledForUser($app);
317
	}
318
319
	/**
320
	 * enables an app
321
	 *
322
	 * @param mixed $app app
323
	 * @param array $groups (optional) when set, only these groups will have access to the app
324
	 * @throws \Exception
325
	 * @return void
326
	 *
327
	 * This function set an app as enabled in appconfig.
328
	 */
329
	public static function enable($app, $groups = null) {
330
		self::$enabledAppsCache = array(); // flush
331
		if (!Installer::isInstalled($app)) {
332
			$app = self::installApp($app);
333
		}
334
335
		$appManager = \OC::$server->getAppManager();
336
		if (!is_null($groups)) {
337
			$groupManager = \OC::$server->getGroupManager();
338
			$groupsList = [];
339
			foreach ($groups as $group) {
340
				$groupItem = $groupManager->get($group);
341
				if ($groupItem instanceof \OCP\IGroup) {
342
					$groupsList[] = $groupManager->get($group);
343
				}
344
			}
345
			$appManager->enableAppForGroups($app, $groupsList);
346
		} else {
347
			$appManager->enableApp($app);
348
		}
349
350
		$info = self::getAppInfo($app);
351 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...
352
			$appPath = self::getAppPath($app);
353
			self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 352 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...
354
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
355
		}
356
	}
357
358
	/**
359
	 * @param string $app
360
	 * @return int
361
	 */
362
	private static function downloadApp($app) {
363
		$ocsClient = new OCSClient(
364
			\OC::$server->getHTTPClientService(),
365
			\OC::$server->getConfig(),
366
			\OC::$server->getLogger()
367
		);
368
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
369
		$download = $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion());
370
		if(isset($download['downloadlink']) and $download['downloadlink']!='') {
371
			// Replace spaces in download link without encoding entire URL
372
			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
373
			$info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData);
374
			$app = Installer::installApp($info);
375
		}
376
		return $app;
377
	}
378
379
	/**
380
	 * @param string $app
381
	 * @return bool
382
	 */
383
	public static function removeApp($app) {
384
		if (self::isShipped($app)) {
385
			return false;
386
		}
387
388
		return Installer::removeApp($app);
389
	}
390
391
	/**
392
	 * This function set an app as disabled in appconfig.
393
	 *
394
	 * @param string $app app
395
	 * @throws Exception
396
	 */
397
	public static function disable($app) {
398
		// Convert OCS ID to regular application identifier
399
		if(self::getInternalAppIdByOcs($app) !== false) {
400
			$app = self::getInternalAppIdByOcs($app);
401
		}
402
403
		// flush
404
		self::$enabledAppsCache = array();
405
406
		// run uninstall steps
407
		$appData = OC_App::getAppInfo($app);
408
		if (!is_null($appData)) {
409
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
410
		}
411
412
		// emit disable hook - needed anymore ?
413
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
414
415
		// finally disable it
416
		$appManager = \OC::$server->getAppManager();
417
		$appManager->disableApp($app);
418
	}
419
420
	/**
421
	 * Returns the Settings Navigation
422
	 *
423
	 * @return string[]
424
	 *
425
	 * This function returns an array containing all settings pages added. The
426
	 * entries are sorted by the key 'order' ascending.
427
	 */
428
	public static function getSettingsNavigation() {
429
		$l = \OC::$server->getL10N('lib');
430
		$urlGenerator = \OC::$server->getURLGenerator();
431
432
		$settings = array();
433
		// by default, settings only contain the help menu
434
		if (OC_Util::getEditionString() === '' &&
435
			\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true
436
		) {
437
			$settings = array(
438
				array(
439
					"id" => "help",
440
					"order" => 1000,
441
					"href" => $urlGenerator->linkToRoute('settings_help'),
442
					"name" => $l->t("Help"),
443
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
444
				)
445
			);
446
		}
447
448
		// if the user is logged-in
449
		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...
450
			// personal menu
451
			$settings[] = array(
452
				"id" => "personal",
453
				"order" => 1,
454
				"href" => $urlGenerator->linkToRoute('settings_personal'),
455
				"name" => $l->t("Personal"),
456
				"icon" => $urlGenerator->imagePath("settings", "personal.svg")
457
			);
458
459
			//SubAdmins are also allowed to access user management
460
			$userObject = \OC::$server->getUserSession()->getUser();
461
			$isSubAdmin = false;
462
			if($userObject !== null) {
463
				$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
464
			}
465
			if ($isSubAdmin) {
466
				// admin users menu
467
				$settings[] = array(
468
					"id" => "core_users",
469
					"order" => 2,
470
					"href" => $urlGenerator->linkToRoute('settings_users'),
471
					"name" => $l->t("Users"),
472
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
473
				);
474
			}
475
476
			// if the user is an admin
477
			if (OC_User::isAdminUser(OC_User::getUser())) {
478
				// admin settings
479
				$settings[] = array(
480
					"id" => "admin",
481
					"order" => 1000,
482
					"href" => $urlGenerator->linkToRoute('settings.AdminSettings.index'),
483
					"name" => $l->t("Admin"),
484
					"icon" => $urlGenerator->imagePath("settings", "admin.svg")
485
				);
486
			}
487
		}
488
489
		$navigation = self::proceedNavigation($settings);
490
		return $navigation;
491
	}
492
493
	// This is private as well. It simply works, so don't ask for more details
494
	private static function proceedNavigation($list) {
495
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
496
		foreach ($list as &$navEntry) {
497
			if ($navEntry['id'] == $activeApp) {
498
				$navEntry['active'] = true;
499
			} else {
500
				$navEntry['active'] = false;
501
			}
502
		}
503
		unset($navEntry);
504
505
		usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}'));
506
507
		return $list;
508
	}
509
510
	/**
511
	 * Get the path where to install apps
512
	 *
513
	 * @return string|false
514
	 */
515
	public static function getInstallPath() {
516
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
517
			return false;
518
		}
519
520
		foreach (OC::$APPSROOTS as $dir) {
521
			if (isset($dir['writable']) && $dir['writable'] === true) {
522
				return $dir['path'];
523
			}
524
		}
525
526
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
527
		return null;
528
	}
529
530
531
	/**
532
	 * search for an app in all app-directories
533
	 *
534
	 * @param string $appId
535
	 * @return false|string
536
	 */
537
	protected static function findAppInDirectories($appId) {
538
		$sanitizedAppId = self::cleanAppId($appId);
539
		if($sanitizedAppId !== $appId) {
540
			return false;
541
		}
542
		static $app_dir = array();
543
544
		if (isset($app_dir[$appId])) {
545
			return $app_dir[$appId];
546
		}
547
548
		$possibleApps = array();
549
		foreach (OC::$APPSROOTS as $dir) {
550
			if (file_exists($dir['path'] . '/' . $appId)) {
551
				$possibleApps[] = $dir;
552
			}
553
		}
554
555
		if (empty($possibleApps)) {
556
			return false;
557
		} elseif (count($possibleApps) === 1) {
558
			$dir = array_shift($possibleApps);
559
			$app_dir[$appId] = $dir;
560
			return $dir;
561
		} else {
562
			$versionToLoad = array();
563
			foreach ($possibleApps as $possibleApp) {
564
				$version = self::getAppVersionByPath($possibleApp['path']);
565
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
566
					$versionToLoad = array(
567
						'dir' => $possibleApp,
568
						'version' => $version,
569
					);
570
				}
571
			}
572
			$app_dir[$appId] = $versionToLoad['dir'];
573
			return $versionToLoad['dir'];
574
			//TODO - write test
575
		}
576
	}
577
578
	/**
579
	 * Get the directory for the given app.
580
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
581
	 *
582
	 * @param string $appId
583
	 * @return string|false
584
	 */
585
	public static function getAppPath($appId) {
586
		if ($appId === null || trim($appId) === '') {
587
			return false;
588
		}
589
590
		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...
591
			return $dir['path'] . '/' . $appId;
592
		}
593
		return false;
594
	}
595
596
597
	/**
598
	 * check if an app's directory is writable
599
	 *
600
	 * @param string $appId
601
	 * @return bool
602
	 */
603
	public static function isAppDirWritable($appId) {
604
		$path = self::getAppPath($appId);
605
		return ($path !== false) ? is_writable($path) : false;
606
	}
607
608
	/**
609
	 * Get the path for the given app on the access
610
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
611
	 *
612
	 * @param string $appId
613
	 * @return string|false
614
	 */
615
	public static function getAppWebPath($appId) {
616
		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...
617
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
618
		}
619
		return false;
620
	}
621
622
	/**
623
	 * get the last version of the app from appinfo/info.xml
624
	 *
625
	 * @param string $appId
626
	 * @return string
627
	 */
628
	public static function getAppVersion($appId) {
629
		if (!isset(self::$appVersion[$appId])) {
630
			$file = self::getAppPath($appId);
631
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
632
		}
633
		return self::$appVersion[$appId];
634
	}
635
636
	/**
637
	 * get app's version based on it's path
638
	 *
639
	 * @param string $path
640
	 * @return string
641
	 */
642
	public static function getAppVersionByPath($path) {
643
		$infoFile = $path . '/appinfo/info.xml';
644
		$appData = self::getAppInfo($infoFile, true);
645
		return isset($appData['version']) ? $appData['version'] : '';
646
	}
647
648
649
	/**
650
	 * Read all app metadata from the info.xml file
651
	 *
652
	 * @param string $appId id of the app or the path of the info.xml file
653
	 * @param boolean $path (optional)
654
	 * @return array|null
655
	 * @note all data is read from info.xml, not just pre-defined fields
656
	 */
657
	public static function getAppInfo($appId, $path = false) {
658
		if ($path) {
659
			$file = $appId;
660
		} else {
661
			if (isset(self::$appInfo[$appId])) {
662
				return self::$appInfo[$appId];
663
			}
664
			$appPath = self::getAppPath($appId);
665
			if($appPath === false) {
666
				return null;
667
			}
668
			$file = $appPath . '/appinfo/info.xml';
669
		}
670
671
		$parser = new \OC\App\InfoParser(\OC::$server->getURLGenerator());
672
		$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 686 which is incompatible with the return type documented by OC_App::getAppInfo of type array|null.
Loading history...
673
674
		if (is_array($data)) {
675
			$data = OC_App::parseAppInfo($data);
676
		}
677
		if(isset($data['ocsid'])) {
678
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
679
			if($storedId !== '' && $storedId !== $data['ocsid']) {
680
				$data['ocsid'] = $storedId;
681
			}
682
		}
683
684
		self::$appInfo[$appId] = $data;
685
686
		return $data;
687
	}
688
689
	/**
690
	 * Returns the navigation
691
	 *
692
	 * @return array
693
	 *
694
	 * This function returns an array containing all entries added. The
695
	 * entries are sorted by the key 'order' ascending. Additional to the keys
696
	 * given for each app the following keys exist:
697
	 *   - active: boolean, signals if the user is on this navigation entry
698
	 */
699
	public static function getNavigation() {
700
		$entries = OC::$server->getNavigationManager()->getAll();
701
		$navigation = self::proceedNavigation($entries);
702
		return $navigation;
703
	}
704
705
	/**
706
	 * get the id of loaded app
707
	 *
708
	 * @return string
709
	 */
710
	public static function getCurrentApp() {
711
		$request = \OC::$server->getRequest();
712
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
713
		$topFolder = substr($script, 0, strpos($script, '/'));
714
		if (empty($topFolder)) {
715
			$path_info = $request->getPathInfo();
716
			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...
717
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
718
			}
719
		}
720
		if ($topFolder == 'apps') {
721
			$length = strlen($topFolder);
722
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
723
		} else {
724
			return $topFolder;
725
		}
726
	}
727
728
	/**
729
	 * @param string $type
730
	 * @return array
731
	 */
732
	public static function getForms($type) {
733
		$forms = array();
734
		switch ($type) {
735
			case 'admin':
736
				$source = self::$adminForms;
737
				break;
738
			case 'personal':
739
				$source = self::$personalForms;
740
				break;
741
			default:
742
				return array();
743
		}
744
		foreach ($source as $form) {
745
			$forms[] = include $form;
746
		}
747
		return $forms;
748
	}
749
750
	/**
751
	 * register an admin form to be shown
752
	 *
753
	 * @param string $app
754
	 * @param string $page
755
	 */
756
	public static function registerAdmin($app, $page) {
757
		self::$adminForms[] = $app . '/' . $page . '.php';
758
	}
759
760
	/**
761
	 * register a personal form to be shown
762
	 * @param string $app
763
	 * @param string $page
764
	 */
765
	public static function registerPersonal($app, $page) {
766
		self::$personalForms[] = $app . '/' . $page . '.php';
767
	}
768
769
	/**
770
	 * @param array $entry
771
	 */
772
	public static function registerLogIn(array $entry) {
773
		self::$altLogin[] = $entry;
774
	}
775
776
	/**
777
	 * @return array
778
	 */
779
	public static function getAlternativeLogIns() {
780
		return self::$altLogin;
781
	}
782
783
	/**
784
	 * get a list of all apps in the apps folder
785
	 *
786
	 * @return array an array of app names (string IDs)
787
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
788
	 */
789
	public static function getAllApps() {
790
791
		$apps = array();
792
793
		foreach (OC::$APPSROOTS as $apps_dir) {
794
			if (!is_readable($apps_dir['path'])) {
795
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
796
				continue;
797
			}
798
			$dh = opendir($apps_dir['path']);
799
800
			if (is_resource($dh)) {
801
				while (($file = readdir($dh)) !== false) {
802
803
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
804
805
						$apps[] = $file;
806
					}
807
				}
808
			}
809
		}
810
811
		return $apps;
812
	}
813
814
	/**
815
	 * List all apps, this is used in apps.php
816
	 *
817
	 * @param bool $onlyLocal
818
	 * @param bool $includeUpdateInfo Should we check whether there is an update
819
	 *                                in the app store?
820
	 * @param OCSClient $ocsClient
821
	 * @return array
822
	 */
823
	public static function listAllApps($onlyLocal = false,
824
									   $includeUpdateInfo = true,
825
									   OCSClient $ocsClient) {
826
		$installedApps = OC_App::getAllApps();
827
828
		//TODO which apps do we want to blacklist and how do we integrate
829
		// blacklisting with the multi apps folder feature?
830
831
		//we don't want to show configuration for these
832
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
833
		$appList = array();
834
835
		foreach ($installedApps as $app) {
836
			if (array_search($app, $blacklist) === false) {
837
838
				$info = OC_App::getAppInfo($app);
839
				if (!is_array($info)) {
840
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
841
					continue;
842
				}
843
844
				if (!isset($info['name'])) {
845
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
846
					continue;
847
				}
848
849
				$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...
850
				$info['groups'] = null;
851
				if ($enabled === 'yes') {
852
					$active = true;
853
				} else if ($enabled === 'no') {
854
					$active = false;
855
				} else {
856
					$active = true;
857
					$info['groups'] = $enabled;
858
				}
859
860
				$info['active'] = $active;
861
862
				if (self::isShipped($app)) {
863
					$info['internal'] = true;
864
					$info['level'] = self::officialApp;
865
					$info['removable'] = false;
866
				} else {
867
					$info['internal'] = false;
868
					$info['removable'] = true;
869
				}
870
871
				$info['update'] = ($includeUpdateInfo) ? Installer::isUpdateAvailable($app) : null;
872
873
				$appPath = self::getAppPath($app);
874
				if($appPath !== false) {
875
					$appIcon = $appPath . '/img/' . $app . '.svg';
876
					if (file_exists($appIcon)) {
877
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
878
						$info['previewAsIcon'] = true;
879
					} else {
880
						$appIcon = $appPath . '/img/app.svg';
881
						if (file_exists($appIcon)) {
882
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
883
							$info['previewAsIcon'] = true;
884
						}
885
					}
886
				}
887
				$info['version'] = OC_App::getAppVersion($app);
888
				$appList[] = $info;
889
			}
890
		}
891
		if ($onlyLocal) {
892
			$remoteApps = [];
893
		} else {
894
			$remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient);
895
		}
896
		if ($remoteApps) {
897
			// Remove duplicates
898
			foreach ($appList as $app) {
899
				foreach ($remoteApps AS $key => $remote) {
900
					if ($app['name'] === $remote['name'] ||
901
						(isset($app['ocsid']) &&
902
							$app['ocsid'] === $remote['id'])
903
					) {
904
						unset($remoteApps[$key]);
905
					}
906
				}
907
			}
908
			$combinedApps = array_merge($appList, $remoteApps);
909
		} else {
910
			$combinedApps = $appList;
911
		}
912
913
		return $combinedApps;
914
	}
915
916
	/**
917
	 * Returns the internal app ID or false
918
	 * @param string $ocsID
919
	 * @return string|false
920
	 */
921
	public static function getInternalAppIdByOcs($ocsID) {
922
		if(is_numeric($ocsID)) {
923
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
924
			if(array_search($ocsID, $idArray)) {
925
				return array_search($ocsID, $idArray);
926
			}
927
		}
928
		return false;
929
	}
930
931
	/**
932
	 * Get a list of all apps on the appstore
933
	 * @param string $filter
934
	 * @param string|null $category
935
	 * @param OCSClient $ocsClient
936
	 * @return array|bool  multi-dimensional array of apps.
937
	 *                     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
938
	 */
939
	public static function getAppstoreApps($filter = 'approved',
940
										   $category = null,
941
										   OCSClient $ocsClient) {
942
		$categories = [$category];
943
944
		if (is_null($category)) {
945
			$categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion());
946
			if (is_array($categoryNames)) {
947
				// Check that categories of apps were retrieved correctly
948
				if (!$categories = array_keys($categoryNames)) {
949
					return false;
950
				}
951
			} else {
952
				return false;
953
			}
954
		}
955
956
		$page = 0;
957
		$remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion());
958
		$apps = [];
959
		$i = 0;
960
		$l = \OC::$server->getL10N('core');
961
		foreach ($remoteApps as $app) {
962
			$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
963
			// enhance app info (for example the description)
964
			$apps[$i] = OC_App::parseAppInfo($app);
965
			$apps[$i]['author'] = $app['personid'];
966
			$apps[$i]['ocs_id'] = $app['id'];
967
			$apps[$i]['internal'] = 0;
968
			$apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
969
			$apps[$i]['update'] = false;
970
			$apps[$i]['groups'] = false;
971
			$apps[$i]['score'] = $app['score'];
972
			$apps[$i]['removable'] = false;
973
			if ($app['label'] == 'recommended') {
974
				$apps[$i]['internallabel'] = (string)$l->t('Recommended');
975
				$apps[$i]['internalclass'] = 'recommendedapp';
976
			}
977
978
			// Apps from the appstore are always assumed to be compatible with the
979
			// the current release as the initial filtering is done on the appstore
980
			$apps[$i]['dependencies']['owncloud']['@attributes']['min-version'] = implode('.', \OCP\Util::getVersion());
981
			$apps[$i]['dependencies']['owncloud']['@attributes']['max-version'] = implode('.', \OCP\Util::getVersion());
982
983
			$i++;
984
		}
985
986
987
988
		if (empty($apps)) {
989
			return false;
990
		} else {
991
			return $apps;
992
		}
993
	}
994
995
	public static function shouldUpgrade($app) {
996
		$versions = self::getAppVersions();
997
		$currentVersion = OC_App::getAppVersion($app);
998
		if ($currentVersion && isset($versions[$app])) {
999
			$installedVersion = $versions[$app];
1000
			if (!version_compare($currentVersion, $installedVersion, '=')) {
1001
				return true;
1002
			}
1003
		}
1004
		return false;
1005
	}
1006
1007
	/**
1008
	 * Adjust the number of version parts of $version1 to match
1009
	 * the number of version parts of $version2.
1010
	 *
1011
	 * @param string $version1 version to adjust
1012
	 * @param string $version2 version to take the number of parts from
1013
	 * @return string shortened $version1
1014
	 */
1015
	private static function adjustVersionParts($version1, $version2) {
1016
		$version1 = explode('.', $version1);
1017
		$version2 = explode('.', $version2);
1018
		// reduce $version1 to match the number of parts in $version2
1019
		while (count($version1) > count($version2)) {
1020
			array_pop($version1);
1021
		}
1022
		// if $version1 does not have enough parts, add some
1023
		while (count($version1) < count($version2)) {
1024
			$version1[] = '0';
1025
		}
1026
		return implode('.', $version1);
1027
	}
1028
1029
	/**
1030
	 * Check whether the current ownCloud version matches the given
1031
	 * application's version requirements.
1032
	 *
1033
	 * The comparison is made based on the number of parts that the
1034
	 * app info version has. For example for ownCloud 6.0.3 if the
1035
	 * app info version is expecting version 6.0, the comparison is
1036
	 * made on the first two parts of the ownCloud version.
1037
	 * This means that it's possible to specify "requiremin" => 6
1038
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
1039
	 *
1040
	 * @param string $ocVersion ownCloud version to check against
1041
	 * @param array $appInfo app info (from xml)
1042
	 *
1043
	 * @return boolean true if compatible, otherwise false
1044
	 */
1045
	public static function isAppCompatible($ocVersion, $appInfo) {
1046
		$requireMin = '';
1047
		$requireMax = '';
1048
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
1049
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1050
		} else if (isset($appInfo['requiremin'])) {
1051
			$requireMin = $appInfo['requiremin'];
1052
		} else if (isset($appInfo['require'])) {
1053
			$requireMin = $appInfo['require'];
1054
		}
1055
1056
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
1057
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1058
		} else if (isset($appInfo['requiremax'])) {
1059
			$requireMax = $appInfo['requiremax'];
1060
		}
1061
1062
		if (is_array($ocVersion)) {
1063
			$ocVersion = implode('.', $ocVersion);
1064
		}
1065
1066 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...
1067
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1068
		) {
1069
1070
			return false;
1071
		}
1072
1073 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...
1074
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1075
		) {
1076
			return false;
1077
		}
1078
1079
		return true;
1080
	}
1081
1082
	/**
1083
	 * get the installed version of all apps
1084
	 */
1085
	public static function getAppVersions() {
1086
		static $versions;
1087
1088
		if(!$versions) {
1089
			$appConfig = \OC::$server->getAppConfig();
1090
			$versions = $appConfig->getValues(false, 'installed_version');
1091
		}
1092
		return $versions;
1093
	}
1094
1095
	/**
1096
	 * @param string $app
1097
	 * @return bool
1098
	 * @throws Exception if app is not compatible with this version of ownCloud
1099
	 * @throws Exception if no app-name was specified
1100
	 */
1101
	public static function installApp($app) {
1102
		$appName = $app; // $app will be overwritten, preserve name for error logging
1103
		$l = \OC::$server->getL10N('core');
1104
		$config = \OC::$server->getConfig();
1105
		$ocsClient = new OCSClient(
1106
			\OC::$server->getHTTPClientService(),
1107
			$config,
1108
			\OC::$server->getLogger()
1109
		);
1110
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
1111
1112
		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
1113
		if (!is_numeric($app)) {
1114
			$shippedVersion = self::getAppVersion($app);
1115
			if ($appData && version_compare($shippedVersion, $appData['version'], '<')) {
1116
				$app = self::downloadApp($app);
1117
			} else {
1118
				$app = Installer::installShippedApp($app);
1119
			}
1120
		} else {
1121
			// Maybe the app is already installed - compare the version in this
1122
			// case and use the local already installed one.
1123
			// FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me.
1124
			$internalAppId = self::getInternalAppIdByOcs($app);
1125
			if($internalAppId !== false) {
1126
				if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) {
1127
					$app = self::downloadApp($app);
1128
				} else {
1129
					self::enable($internalAppId);
1130
					$app = $internalAppId;
1131
				}
1132
			} else {
1133
				$app = self::downloadApp($app);
1134
			}
1135
		}
1136
1137
		if ($app !== false) {
1138
			// check if the app is compatible with this version of ownCloud
1139
			$info = self::getAppInfo($app);
1140
			if(!is_array($info)) {
1141
				throw new \Exception(
1142
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1143
						[$info['name']]
1144
					)
1145
				);
1146
			}
1147
1148
			$version = \OCP\Util::getVersion();
1149
			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...
1150
				throw new \Exception(
1151
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1152
						array($info['name'])
1153
					)
1154
				);
1155
			}
1156
1157
			// check for required dependencies
1158
			$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1159
			$missing = $dependencyAnalyzer->analyze($info);
1160
			if (!empty($missing)) {
1161
				$missingMsg = join(PHP_EOL, $missing);
1162
				throw new \Exception(
1163
					$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1164
						array($info['name'], $missingMsg)
1165
					)
1166
				);
1167
			}
1168
1169
			$config->setAppValue($app, 'enabled', 'yes');
1170
			if (isset($appData['id'])) {
1171
				$config->setAppValue($app, 'ocsid', $appData['id']);
1172
			}
1173
1174 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...
1175
				$appPath = self::getAppPath($app);
1176
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1175 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...
1177
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1178
			}
1179
1180
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1181
		} else {
1182
			if(empty($appName) ) {
1183
				throw new \Exception($l->t("No app name specified"));
1184
			} else {
1185
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
0 ignored issues
show
Documentation introduced by
$appName is of type string, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1186
			}
1187
		}
1188
1189
		return $app;
1190
	}
1191
1192
	/**
1193
	 * update the database for the app and call the update script
1194
	 *
1195
	 * @param string $appId
1196
	 * @return bool
1197
	 */
1198
	public static function updateApp($appId) {
1199
		$appPath = self::getAppPath($appId);
1200
		if($appPath === false) {
1201
			return false;
1202
		}
1203
		$appData = self::getAppInfo($appId);
1204
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1205
		if (file_exists($appPath . '/appinfo/database.xml')) {
1206
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1207
		}
1208
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1209
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1210
		unset(self::$appVersion[$appId]);
1211
		// run upgrade code
1212
		if (file_exists($appPath . '/appinfo/update.php')) {
1213
			self::loadApp($appId, false);
1214
			include $appPath . '/appinfo/update.php';
1215
		}
1216
		self::setupBackgroundJobs($appData['background-jobs']);
1217 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...
1218
			$appPath = self::getAppPath($appId);
1219
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 1218 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...
1220
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1221
		}
1222
1223
		//set remote/public handlers
1224
		if (array_key_exists('ocsid', $appData)) {
1225
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1226 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...
1227
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1228
		}
1229
		foreach ($appData['remote'] as $name => $path) {
1230
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1231
		}
1232 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...
1233
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1234
		}
1235
1236
		self::setAppTypes($appId);
1237
1238
		$version = \OC_App::getAppVersion($appId);
1239
		\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...
1240
1241
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1242
			ManagerEvent::EVENT_APP_UPDATE, $appId
1243
		));
1244
1245
		return true;
1246
	}
1247
1248
	/**
1249
	 * @param string $appId
1250
	 * @param string[] $steps
1251
	 * @throws \OC\NeedsUpdateException
1252
	 */
1253
	public static function executeRepairSteps($appId, array $steps) {
1254
		if (empty($steps)) {
1255
			return;
1256
		}
1257
		// load the app
1258
		self::loadApp($appId, false);
1259
1260
		$dispatcher = OC::$server->getEventDispatcher();
1261
1262
		// load the steps
1263
		$r = new Repair([], $dispatcher);
1264
		foreach ($steps as $step) {
1265
			try {
1266
				$r->addStep($step);
1267
			} catch (Exception $ex) {
1268
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1269
				\OC::$server->getLogger()->logException($ex);
1270
			}
1271
		}
1272
		// run the steps
1273
		$r->run();
1274
	}
1275
1276
	public static function setupBackgroundJobs(array $jobs) {
1277
		$queue = \OC::$server->getJobList();
1278
		foreach ($jobs as $job) {
1279
			$queue->add($job);
1280
		}
1281
	}
1282
1283
	/**
1284
	 * @param string $appId
1285
	 * @param string[] $steps
1286
	 */
1287
	private static function setupLiveMigrations($appId, array $steps) {
1288
		$queue = \OC::$server->getJobList();
1289
		foreach ($steps as $step) {
1290
			$queue->add('OC\Migration\BackgroundRepair', [
1291
				'app' => $appId,
1292
				'step' => $step]);
1293
		}
1294
	}
1295
1296
	/**
1297
	 * @param string $appId
1298
	 * @return \OC\Files\View|false
1299
	 */
1300
	public static function getStorage($appId) {
1301
		if (OC_App::isEnabled($appId)) { //sanity check
1302
			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...
1303
				$view = new \OC\Files\View('/' . OC_User::getUser());
1304
				if (!$view->file_exists($appId)) {
1305
					$view->mkdir($appId);
1306
				}
1307
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1308
			} else {
1309
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1310
				return false;
1311
			}
1312
		} else {
1313
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1314
			return false;
1315
		}
1316
	}
1317
1318
	/**
1319
	 * parses the app data array and enhanced the 'description' value
1320
	 *
1321
	 * @param array $data the app data
1322
	 * @return array improved app data
1323
	 */
1324
	public static function parseAppInfo(array $data) {
1325
1326
		// just modify the description if it is available
1327
		// otherwise this will create a $data element with an empty 'description'
1328
		if (isset($data['description'])) {
1329
			if (is_string($data['description'])) {
1330
				// sometimes the description contains line breaks and they are then also
1331
				// shown in this way in the app management which isn't wanted as HTML
1332
				// manages line breaks itself
1333
1334
				// first of all we split on empty lines
1335
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1336
1337
				$result = [];
1338
				foreach ($paragraphs as $value) {
1339
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1340
					// with a single space - also trims whitespace
1341
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1342
				}
1343
1344
				// join the single paragraphs with a empty line in between
1345
				$data['description'] = implode("\n\n", $result);
1346
1347
			} else {
1348
				$data['description'] = '';
1349
			}
1350
		}
1351
1352
		return $data;
1353
	}
1354
}
1355