Completed
Push — master ( c77917...12b0e9 )
by Jörn Friedrich
57:44
created

OC_App::isAppLoaded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
778
779 4
		foreach ($installedApps as $app) {
780 4
			if (array_search($app, $blacklist) === false) {
781
782 4
				$info = OC_App::getAppInfo($app);
783
784 4
				if (!isset($info['name'])) {
785
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
786
					continue;
787
				}
788
789 4
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
790 4
				$info['groups'] = null;
791 4
				if ($enabled === 'yes') {
792 4
					$active = true;
793 4
				} else if ($enabled === 'no') {
794 4
					$active = false;
795 4
				} else {
796
					$active = true;
797
					$info['groups'] = $enabled;
798
				}
799
800 4
				$info['active'] = $active;
801
802 4
				if (self::isShipped($app)) {
803 4
					$info['internal'] = true;
804 4
					$info['level'] = self::officialApp;
805 4
					$info['removable'] = false;
806 4
				} else {
807
					$info['internal'] = false;
808
					$info['removable'] = true;
809
				}
810
811 4
				$info['update'] = ($includeUpdateInfo) ? OC_Installer::isUpdateAvailable($app) : null;
812
813 4
				$appIcon = self::getAppPath($app) . '/img/' . $app . '.svg';
814 4
				if (file_exists($appIcon)) {
815
					$info['preview'] = OC_Helper::imagePath($app, $app . '.svg');
816
					$info['previewAsIcon'] = true;
817
				} else {
818 4
					$appIcon = self::getAppPath($app) . '/img/app.svg';
819 4
					if (file_exists($appIcon)) {
820 4
						$info['preview'] = OC_Helper::imagePath($app, 'app.svg');
821 4
						$info['previewAsIcon'] = true;
822 4
					}
823
				}
824 4
				$info['version'] = OC_App::getAppVersion($app);
825 4
				$appList[] = $info;
826 4
			}
827 4
		}
828 4
		if ($onlyLocal) {
829
			$remoteApps = [];
830
		} else {
831 4
			$remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient);
832
		}
833 4
		if ($remoteApps) {
834
			// Remove duplicates
835
			foreach ($appList as $app) {
836
				foreach ($remoteApps AS $key => $remote) {
837
					if ($app['name'] === $remote['name'] ||
838
						(isset($app['ocsid']) &&
839
							$app['ocsid'] === $remote['id'])
840
					) {
841
						unset($remoteApps[$key]);
842
					}
843
				}
844
			}
845
			$combinedApps = array_merge($appList, $remoteApps);
846
		} else {
847 4
			$combinedApps = $appList;
848
		}
849
850 4
		return $combinedApps;
851
	}
852
853
	/**
854
	 * Returns the internal app ID or false
855
	 * @param string $ocsID
856
	 * @return string|false
857
	 */
858
	protected static function getInternalAppIdByOcs($ocsID) {
859
		if(is_numeric($ocsID)) {
860
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
861
			if(array_search($ocsID, $idArray)) {
862
				return array_search($ocsID, $idArray);
863
			}
864
		}
865
		return false;
866
	}
867
868
	/**
869
	 * Get a list of all apps on the appstore
870
	 * @param string $filter
871
	 * @param string|null $category
872
	 * @param OCSClient $ocsClient
873
	 * @return array|bool  multi-dimensional array of apps.
874
	 *                     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
875
	 */
876 4
	public static function getAppstoreApps($filter = 'approved',
877
										   $category = null,
878
										   OCSClient $ocsClient) {
879 4
		$categories = [$category];
880
881 4
		if (is_null($category)) {
882 4
			$categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion());
883 4
			if (is_array($categoryNames)) {
884
				// Check that categories of apps were retrieved correctly
885
				if (!$categories = array_keys($categoryNames)) {
886
					return false;
887
				}
888
			} else {
889 4
				return false;
890
			}
891
		}
892
893
		$page = 0;
894
		$remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion());
895
		$apps = [];
896
		$i = 0;
897
		$l = \OC::$server->getL10N('core');
898
		foreach ($remoteApps as $app) {
899
			$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
900
			// enhance app info (for example the description)
901
			$apps[$i] = OC_App::parseAppInfo($app);
902
			$apps[$i]['author'] = $app['personid'];
903
			$apps[$i]['ocs_id'] = $app['id'];
904
			$apps[$i]['internal'] = 0;
905
			$apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
906
			$apps[$i]['update'] = false;
907
			$apps[$i]['groups'] = false;
908
			$apps[$i]['score'] = $app['score'];
909
			$apps[$i]['removable'] = false;
910
			if ($app['label'] == 'recommended') {
911
				$apps[$i]['internallabel'] = (string)$l->t('Recommended');
912
				$apps[$i]['internalclass'] = 'recommendedapp';
913
			}
914
915
			$i++;
916
		}
917
918
919
920
		if (empty($apps)) {
921
			return false;
922
		} else {
923
			return $apps;
924
		}
925
	}
926
927 13
	public static function shouldUpgrade($app) {
928 13
		$versions = self::getAppVersions();
929 13
		$currentVersion = OC_App::getAppVersion($app);
930 13
		if ($currentVersion && isset($versions[$app])) {
931 13
			$installedVersion = $versions[$app];
932 13
			if (version_compare($currentVersion, $installedVersion, '>')) {
933
				return true;
934
			}
935 13
		}
936 13
		return false;
937
	}
938
939
	/**
940
	 * Adjust the number of version parts of $version1 to match
941
	 * the number of version parts of $version2.
942
	 *
943
	 * @param string $version1 version to adjust
944
	 * @param string $version2 version to take the number of parts from
945
	 * @return string shortened $version1
946
	 */
947 31
	private static function adjustVersionParts($version1, $version2) {
948 31
		$version1 = explode('.', $version1);
949 31
		$version2 = explode('.', $version2);
950
		// reduce $version1 to match the number of parts in $version2
951 31
		while (count($version1) > count($version2)) {
952 24
			array_pop($version1);
953 24
		}
954
		// if $version1 does not have enough parts, add some
955 31
		while (count($version1) < count($version2)) {
956 1
			$version1[] = '0';
957 1
		}
958 31
		return implode('.', $version1);
959
	}
960
961
	/**
962
	 * Check whether the current ownCloud version matches the given
963
	 * application's version requirements.
964
	 *
965
	 * The comparison is made based on the number of parts that the
966
	 * app info version has. For example for ownCloud 6.0.3 if the
967
	 * app info version is expecting version 6.0, the comparison is
968
	 * made on the first two parts of the ownCloud version.
969
	 * This means that it's possible to specify "requiremin" => 6
970
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
971
	 *
972
	 * @param string $ocVersion ownCloud version to check against
973
	 * @param array $appInfo app info (from xml)
974
	 *
975
	 * @return boolean true if compatible, otherwise false
976
	 */
977 31
	public static function isAppCompatible($ocVersion, $appInfo) {
978 31
		$requireMin = '';
979 31
		$requireMax = '';
980 31
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
981 3
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
982 31
		} else if (isset($appInfo['requiremin'])) {
983 9
			$requireMin = $appInfo['requiremin'];
984 28
		} else if (isset($appInfo['require'])) {
985 15
			$requireMin = $appInfo['require'];
986 15
		}
987
988 31
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
989 3
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
990 31
		} else if (isset($appInfo['requiremax'])) {
991 11
			$requireMax = $appInfo['requiremax'];
992 11
		}
993
994 31
		if (is_array($ocVersion)) {
995 3
			$ocVersion = implode('.', $ocVersion);
996 3
		}
997
998 31 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...
999 31
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1000 31
		) {
1001
1002 10
			return false;
1003
		}
1004
1005 23 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...
1006 23
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1007 23
		) {
1008
1009 4
			return false;
1010
		}
1011
1012 20
		return true;
1013
	}
1014
1015
	/**
1016
	 * get the installed version of all apps
1017
	 */
1018 13
	public static function getAppVersions() {
1019 13
		static $versions;
1020 13
		if (isset($versions)) { // simple cache, needs to be fixed
1021 13
			return $versions; // when function is used besides in checkUpgrade
1022
		}
1023
		$versions = array();
1024
		try {
1025
			$query = OC_DB::prepare('SELECT `appid`, `configvalue` FROM `*PREFIX*appconfig`'
1026
				. ' WHERE `configkey` = \'installed_version\'');
1027
			$result = $query->execute();
1028
			while ($row = $result->fetchRow()) {
1029
				$versions[$row['appid']] = $row['configvalue'];
1030
			}
1031
			return $versions;
1032
		} catch (\Exception $e) {
1033
			return array();
1034
		}
1035
	}
1036
1037
1038
	/**
1039
	 * @param mixed $app
1040
	 * @return bool
1041
	 * @throws Exception if app is not compatible with this version of ownCloud
1042
	 * @throws Exception if no app-name was specified
1043
	 */
1044
	public static function installApp($app) {
1045
		$l = \OC::$server->getL10N('core');
1046
		$config = \OC::$server->getConfig();
1047
		$ocsClient = new OCSClient(
1048
			\OC::$server->getHTTPClientService(),
1049
			$config,
1050
			\OC::$server->getLogger()
1051
		);
1052
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
1053
1054
		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
1055
		if (!is_numeric($app)) {
1056
			$shippedVersion = self::getAppVersion($app);
1057
			if ($appData && version_compare($shippedVersion, $appData['version'], '<')) {
1058
				$app = self::downloadApp($app);
1059
			} else {
1060
				$app = OC_Installer::installShippedApp($app);
1061
			}
1062
		} else {
1063
			// Maybe the app is already installed - compare the version in this
1064
			// case and use the local already installed one.
1065
			// FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me.
1066
			$internalAppId = self::getInternalAppIdByOcs($app);
1067
			if($internalAppId !== false) {
1068
				if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) {
1069
					$app = self::downloadApp($app);
1070
				} else {
1071
					self::enable($internalAppId);
1072
					$app = $internalAppId;
1073
				}
1074
			} else {
1075
				$app = self::downloadApp($app);
1076
			}
1077
		}
1078
1079
		if ($app !== false) {
1080
			// check if the app is compatible with this version of ownCloud
1081
			$info = self::getAppInfo($app);
1082
			$version = \OCP\Util::getVersion();
1083
			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...
Bug introduced by
It seems like $info defined by self::getAppInfo($app) on line 1081 can also be of type null; however, OC_App::isAppCompatible() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1084
				throw new \Exception(
1085
					$l->t('App "%s" cannot be installed because it is not compatible with this version of ownCloud.',
1086
						array($info['name'])
1087
					)
1088
				);
1089
			}
1090
1091
			// check for required dependencies
1092
			$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1093
			$missing = $dependencyAnalyzer->analyze($info);
0 ignored issues
show
Bug introduced by
It seems like $info defined by self::getAppInfo($app) on line 1081 can also be of type null; however, OC\App\DependencyAnalyzer::analyze() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1094
			if (!empty($missing)) {
1095
				$missingMsg = join(PHP_EOL, $missing);
1096
				throw new \Exception(
1097
					$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1098
						array($info['name'], $missingMsg)
1099
					)
1100
				);
1101
			}
1102
1103
			$config->setAppValue($app, 'enabled', 'yes');
1104
			if (isset($appData['id'])) {
1105
				$config->setAppValue($app, 'ocsid', $appData['id']);
1106
			}
1107
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1108
		} else {
1109
			throw new \Exception($l->t("No app name specified"));
1110
		}
1111
1112
		return $app;
1113
	}
1114
1115
	/**
1116
	 * update the database for the app and call the update script
1117
	 *
1118
	 * @param string $appId
1119
	 * @return bool
1120
	 */
1121 1
	public static function updateApp($appId) {
1122 1
		if (file_exists(self::getAppPath($appId) . '/appinfo/database.xml')) {
1123
			OC_DB::updateDbFromStructure(self::getAppPath($appId) . '/appinfo/database.xml');
1124
		}
1125 1
		unset(self::$appVersion[$appId]);
1126
		// run upgrade code
1127 1
		if (file_exists(self::getAppPath($appId) . '/appinfo/update.php')) {
1128
			self::loadApp($appId, false);
1129
			include self::getAppPath($appId) . '/appinfo/update.php';
1130
		}
1131
1132
		//set remote/public handlers
1133 1
		$appData = self::getAppInfo($appId);
1134 1
		if (array_key_exists('ocsid', $appData)) {
1135
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1136 1 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...
1137
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1138
		}
1139 1
		foreach ($appData['remote'] as $name => $path) {
1140
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1141 1
		}
1142 1
		foreach ($appData['public'] as $name => $path) {
1143
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1144 1
		}
1145
1146 1
		self::setAppTypes($appId);
1147
1148 1
		$version = \OC_App::getAppVersion($appId);
1149 1
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
1150
1151 1
		return true;
1152
	}
1153
1154
	/**
1155
	 * @param string $appId
1156
	 * @return \OC\Files\View|false
1157
	 */
1158
	public static function getStorage($appId) {
1159
		if (OC_App::isEnabled($appId)) { //sanity check
1160
			if (OC_User::isLoggedIn()) {
1161
				$view = new \OC\Files\View('/' . OC_User::getUser());
1162
				if (!$view->file_exists($appId)) {
1163
					$view->mkdir($appId);
1164
				}
1165
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1166
			} else {
1167
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1168
				return false;
1169
			}
1170
		} else {
1171
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1172
			return false;
1173
		}
1174
	}
1175
1176
	/**
1177
	 * parses the app data array and enhanced the 'description' value
1178
	 *
1179
	 * @param array $data the app data
1180
	 * @return array improved app data
1181
	 */
1182 9
	public static function parseAppInfo(array $data) {
1183
1184
		// just modify the description if it is available
1185
		// otherwise this will create a $data element with an empty 'description'
1186 9
		if (isset($data['description'])) {
1187 8
			if (is_string($data['description'])) {
1188
				// sometimes the description contains line breaks and they are then also
1189
				// shown in this way in the app management which isn't wanted as HTML
1190
				// manages line breaks itself
1191
1192
				// first of all we split on empty lines
1193 7
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1194
1195 7
				$result = [];
1196 7
				foreach ($paragraphs as $value) {
1197
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1198
					// with a single space - also trims whitespace
1199 7
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1200 7
				}
1201
1202
				// join the single paragraphs with a empty line in between
1203 7
				$data['description'] = implode("\n\n", $result);
1204
1205 7
			} else {
1206 1
				$data['description'] = '';
1207
			}
1208 8
		}
1209
1210 9
		return $data;
1211
	}
1212
}
1213