Completed
Pull Request — master (#841)
by Blizzz
10:54 queued 01:57
created

OC_App::loadApp()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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