Completed
Push — stable9 ( d7dbec...6fdef8 )
by Roeland
126:06 queued 114:59
created

OC_App::getAppInfo()   D

Complexity

Conditions 10
Paths 26

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 21
nc 26
nop 3
dl 0
loc 33
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @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 Lukas Reschke <[email protected]>
21
 * @author Markus Goetz <[email protected]>
22
 * @author Morris Jobke <[email protected]>
23
 * @author RealRancor <[email protected]>
24
 * @author Robin Appelman <[email protected]>
25
 * @author Robin McCorkell <[email protected]>
26
 * @author Roeland Jago Douma <[email protected]>
27
 * @author Sam Tuke <[email protected]>
28
 * @author Thomas Müller <[email protected]>
29
 * @author Thomas Tanghus <[email protected]>
30
 * @author Tom Needham <[email protected]>
31
 * @author Vincent Petry <[email protected]>
32
 *
33
 * @license AGPL-3.0
34
 *
35
 * This code is free software: you can redistribute it and/or modify
36
 * it under the terms of the GNU Affero General Public License, version 3,
37
 * as published by the Free Software Foundation.
38
 *
39
 * This program is distributed in the hope that it will be useful,
40
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
41
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42
 * GNU Affero General Public License for more details.
43
 *
44
 * You should have received a copy of the GNU Affero General Public License, version 3,
45
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
46
 *
47
 */
48
use OC\App\DependencyAnalyzer;
49
use OC\App\Platform;
50
use OC\OCSClient;
51
52
/**
53
 * This class manages the apps. It allows them to register and integrate in the
54
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
55
 * upgrading and removing apps.
56
 */
57
class OC_App {
58
	static private $appVersion = [];
59
	static private $adminForms = array();
60
	static private $personalForms = array();
61
	static private $appInfo = array();
62
	static private $appTypes = array();
63
	static private $loadedApps = array();
64
	static private $altLogin = array();
65
	const officialApp = 200;
66
67
	/**
68
	 * clean the appId
69
	 *
70
	 * @param string|boolean $app AppId that needs to be cleaned
71
	 * @return string
72
	 */
73
	public static function cleanAppId($app) {
74
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
75
	}
76
77
	/**
78
	 * Check if an app is loaded
79
	 *
80
	 * @param string $app
81
	 * @return bool
82
	 */
83
	public static function isAppLoaded($app) {
84
		return in_array($app, self::$loadedApps, true);
85
	}
86
87
	/**
88
	 * loads all apps
89
	 *
90
	 * @param string[] | string | null $types
91
	 * @return bool
92
	 *
93
	 * This function walks through the ownCloud directory and loads all apps
94
	 * it can find. A directory contains an app if the file /appinfo/info.xml
95
	 * exists.
96
	 *
97
	 * if $types is set, only apps of those types will be loaded
98
	 */
99
	public static function loadApps($types = null) {
100
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
101
			return false;
102
		}
103
		// Load the enabled apps here
104
		$apps = self::getEnabledApps();
105
106
		// Add each apps' folder as allowed class path
107
		foreach($apps as $app) {
108
			$path = self::getAppPath($app);
109
			if($path !== false) {
110
				\OC::$loader->addValidRoot($path);
111
			}
112
		}
113
114
		// prevent app.php from printing output
115
		ob_start();
116
		foreach ($apps as $app) {
117
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
118
				self::loadApp($app);
119
			}
120
		}
121
		ob_end_clean();
122
123
		return true;
124
	}
125
126
	/**
127
	 * load a single app
128
	 *
129
	 * @param string $app
130
	 * @param bool $checkUpgrade whether an upgrade check should be done
131
	 * @throws \OC\NeedsUpdateException
132
	 */
133
	public static function loadApp($app, $checkUpgrade = true) {
134
		self::$loadedApps[] = $app;
135
		$appPath = self::getAppPath($app);
136
		if($appPath === false) {
137
			return;
138
		}
139
		\OC::$loader->addValidRoot($appPath); // in case someone calls loadApp() directly
140
		if (is_file($appPath . '/appinfo/app.php')) {
141
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
142
			if ($checkUpgrade and self::shouldUpgrade($app)) {
143
				throw new \OC\NeedsUpdateException();
144
			}
145
			self::requireAppFile($app);
146
			if (self::isType($app, array('authentication'))) {
147
				// since authentication apps affect the "is app enabled for group" check,
148
				// the enabled apps cache needs to be cleared to make sure that the
149
				// next time getEnableApps() is called it will also include apps that were
150
				// enabled for groups
151
				self::$enabledAppsCache = array();
152
			}
153
			\OC::$server->getEventLogger()->end('load_app_' . $app);
154
		}
155
	}
156
157
	/**
158
	 * Load app.php from the given app
159
	 *
160
	 * @param string $app app name
161
	 */
162
	private static function requireAppFile($app) {
163
		// encapsulated here to avoid variable scope conflicts
164
		require_once $app . '/appinfo/app.php';
165
	}
166
167
	/**
168
	 * check if an app is of a specific type
169
	 *
170
	 * @param string $app
171
	 * @param string|array $types
172
	 * @return bool
173
	 */
174
	public static function isType($app, $types) {
175
		if (is_string($types)) {
176
			$types = array($types);
177
		}
178
		$appTypes = self::getAppTypes($app);
179
		foreach ($types as $type) {
180
			if (array_search($type, $appTypes) !== false) {
181
				return true;
182
			}
183
		}
184
		return false;
185
	}
186
187
	/**
188
	 * get the types of an app
189
	 *
190
	 * @param string $app
191
	 * @return array
192
	 */
193
	private static function getAppTypes($app) {
194
		//load the cache
195
		if (count(self::$appTypes) == 0) {
196
			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...
197
		}
198
199
		if (isset(self::$appTypes[$app])) {
200
			return explode(',', self::$appTypes[$app]);
201
		} else {
202
			return array();
203
		}
204
	}
205
206
	/**
207
	 * read app types from info.xml and cache them in the database
208
	 */
209
	public static function setAppTypes($app) {
210
		$appData = self::getAppInfo($app);
211
		if(!is_array($appData)) {
212
			return;
213
		}
214
215
		if (isset($appData['types'])) {
216
			$appTypes = implode(',', $appData['types']);
217
		} else {
218
			$appTypes = '';
219
		}
220
221
		\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...
222
	}
223
224
	/**
225
	 * check if app is shipped
226
	 *
227
	 * @param string $appId the id of the app to check
228
	 * @return bool
229
	 *
230
	 * Check if an app that is installed is a shipped app or installed from the appstore.
231
	 */
232
	public static function isShipped($appId) {
233
		return \OC::$server->getAppManager()->isShipped($appId);
234
	}
235
236
	/**
237
	 * get all enabled apps
238
	 */
239
	protected static $enabledAppsCache = array();
240
241
	/**
242
	 * Returns apps enabled for the current user.
243
	 *
244
	 * @param bool $forceRefresh whether to refresh the cache
245
	 * @param bool $all whether to return apps for all users, not only the
246
	 * currently logged in one
247
	 * @return string[]
248
	 */
249
	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...
250
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
251
			return array();
252
		}
253
		// in incognito mode or when logged out, $user will be false,
254
		// which is also the case during an upgrade
255
		$appManager = \OC::$server->getAppManager();
256
		if ($all) {
257
			$user = null;
258
		} else {
259
			$user = \OC::$server->getUserSession()->getUser();
260
		}
261
262
		if (is_null($user)) {
263
			$apps = $appManager->getInstalledApps();
264
		} else {
265
			$apps = $appManager->getEnabledAppsForUser($user);
266
		}
267
		$apps = array_filter($apps, function ($app) {
268
			return $app !== 'files';//we add this manually
269
		});
270
		sort($apps);
271
		array_unshift($apps, 'files');
272
		return $apps;
273
	}
274
275
	/**
276
	 * checks whether or not an app is enabled
277
	 *
278
	 * @param string $app app
279
	 * @return bool
280
	 *
281
	 * This function checks whether or not an app is enabled.
282
	 */
283
	public static function isEnabled($app) {
284
		return \OC::$server->getAppManager()->isEnabledForUser($app);
285
	}
286
287
	/**
288
	 * enables an app
289
	 *
290
	 * @param mixed $app app
291
	 * @param array $groups (optional) when set, only these groups will have access to the app
292
	 * @throws \Exception
293
	 * @return void
294
	 *
295
	 * This function set an app as enabled in appconfig.
296
	 */
297
	public static function enable($app, $groups = null) {
298
		self::$enabledAppsCache = array(); // flush
299
		if (!OC_Installer::isInstalled($app)) {
300
			$app = self::installApp($app);
301
		}
302
303
		$appManager = \OC::$server->getAppManager();
304
		if (!is_null($groups)) {
305
			$groupManager = \OC::$server->getGroupManager();
306
			$groupsList = [];
307
			foreach ($groups as $group) {
308
				$groupItem = $groupManager->get($group);
309
				if ($groupItem instanceof \OCP\IGroup) {
310
					$groupsList[] = $groupManager->get($group);
311
				}
312
			}
313
			$appManager->enableAppForGroups($app, $groupsList);
314
		} else {
315
			$appManager->enableApp($app);
316
		}
317
	}
318
319
	/**
320
	 * @param string $app
321
	 * @return int
322
	 */
323
	private static function downloadApp($app) {
324
		$ocsClient = new OCSClient(
325
			\OC::$server->getHTTPClientService(),
326
			\OC::$server->getConfig(),
327
			\OC::$server->getLogger()
328
		);
329
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
330
		$download = $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion());
331
		if(isset($download['downloadlink']) and $download['downloadlink']!='') {
332
			// Replace spaces in download link without encoding entire URL
333
			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
334
			$info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData);
335
			$app = OC_Installer::installApp($info);
336
		}
337
		return $app;
338
	}
339
340
	/**
341
	 * @param string $app
342
	 * @return bool
343
	 */
344
	public static function removeApp($app) {
345
		if (self::isShipped($app)) {
346
			return false;
347
		}
348
349
		return OC_Installer::removeApp($app);
350
	}
351
352
	/**
353
	 * This function set an app as disabled in appconfig.
354
	 *
355
	 * @param string $app app
356
	 * @throws Exception
357
	 */
358
	public static function disable($app) {
359
		// Convert OCS ID to regular application identifier
360
		if(self::getInternalAppIdByOcs($app) !== false) {
361
			$app = self::getInternalAppIdByOcs($app);
362
		}
363
364
		self::$enabledAppsCache = array(); // flush
365
		// check if app is a shipped app or not. if not delete
366
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
367
		$appManager = \OC::$server->getAppManager();
368
		$appManager->disableApp($app);
369
	}
370
371
	/**
372
	 * Returns the Settings Navigation
373
	 *
374
	 * @return string[]
375
	 *
376
	 * This function returns an array containing all settings pages added. The
377
	 * entries are sorted by the key 'order' ascending.
378
	 */
379
	public static function getSettingsNavigation() {
380
		$l = \OC::$server->getL10N('lib');
381
		$urlGenerator = \OC::$server->getURLGenerator();
382
383
		$settings = array();
384
		// by default, settings only contain the help menu
385
		if (OC_Util::getEditionString() === '' &&
386
			\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true
387
		) {
388
			$settings = array(
389
				array(
390
					"id" => "help",
391
					"order" => 1000,
392
					"href" => $urlGenerator->linkToRoute('settings_help'),
393
					"name" => $l->t("Help"),
394
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
395
				)
396
			);
397
		}
398
399
		// if the user is logged-in
400
		if (OC_User::isLoggedIn()) {
401
			// personal menu
402
			$settings[] = array(
403
				"id" => "personal",
404
				"order" => 1,
405
				"href" => $urlGenerator->linkToRoute('settings_personal'),
406
				"name" => $l->t("Personal"),
407
				"icon" => $urlGenerator->imagePath("settings", "personal.svg")
408
			);
409
410
			//SubAdmins are also allowed to access user management
411
			$userObject = \OC::$server->getUserSession()->getUser();
412
			$isSubAdmin = false;
413
			if($userObject !== null) {
414
				$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
415
			}
416
			if ($isSubAdmin) {
417
				// admin users menu
418
				$settings[] = array(
419
					"id" => "core_users",
420
					"order" => 2,
421
					"href" => $urlGenerator->linkToRoute('settings_users'),
422
					"name" => $l->t("Users"),
423
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
424
				);
425
			}
426
427
			// if the user is an admin
428
			if (OC_User::isAdminUser(OC_User::getUser())) {
429
				// admin settings
430
				$settings[] = array(
431
					"id" => "admin",
432
					"order" => 1000,
433
					"href" => $urlGenerator->linkToRoute('settings_admin'),
434
					"name" => $l->t("Admin"),
435
					"icon" => $urlGenerator->imagePath("settings", "admin.svg")
436
				);
437
			}
438
		}
439
440
		$navigation = self::proceedNavigation($settings);
441
		return $navigation;
442
	}
443
444
	// This is private as well. It simply works, so don't ask for more details
445
	private static function proceedNavigation($list) {
446
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
447
		foreach ($list as &$navEntry) {
448
			if ($navEntry['id'] == $activeApp) {
449
				$navEntry['active'] = true;
450
			} else {
451
				$navEntry['active'] = false;
452
			}
453
		}
454
		unset($navEntry);
455
456
		usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}'));
457
458
		return $list;
459
	}
460
461
	/**
462
	 * Get the path where to install apps
463
	 *
464
	 * @return string|false
465
	 */
466
	public static function getInstallPath() {
467
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
468
			return false;
469
		}
470
471
		foreach (OC::$APPSROOTS as $dir) {
472
			if (isset($dir['writable']) && $dir['writable'] === true) {
473
				return $dir['path'];
474
			}
475
		}
476
477
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
478
		return null;
479
	}
480
481
482
	/**
483
	 * search for an app in all app-directories
484
	 *
485
	 * @param string $appId
486
	 * @return false|string
487
	 */
488
	protected static function findAppInDirectories($appId) {
489
		$sanitizedAppId = self::cleanAppId($appId);
490
		if($sanitizedAppId !== $appId) {
491
			return false;
492
		}
493
		static $app_dir = array();
494
495
		if (isset($app_dir[$appId])) {
496
			return $app_dir[$appId];
497
		}
498
499
		$possibleApps = array();
500
		foreach (OC::$APPSROOTS as $dir) {
501
			if (file_exists($dir['path'] . '/' . $appId)) {
502
				$possibleApps[] = $dir;
503
			}
504
		}
505
506
		if (empty($possibleApps)) {
507
			return false;
508
		} elseif (count($possibleApps) === 1) {
509
			$dir = array_shift($possibleApps);
510
			$app_dir[$appId] = $dir;
511
			return $dir;
512
		} else {
513
			$versionToLoad = array();
514
			foreach ($possibleApps as $possibleApp) {
515
				$version = self::getAppVersionByPath($possibleApp['path']);
516
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
517
					$versionToLoad = array(
518
						'dir' => $possibleApp,
519
						'version' => $version,
520
					);
521
				}
522
			}
523
			$app_dir[$appId] = $versionToLoad['dir'];
524
			return $versionToLoad['dir'];
525
			//TODO - write test
526
		}
527
	}
528
529
	/**
530
	 * Get the directory for the given app.
531
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
532
	 *
533
	 * @param string $appId
534
	 * @return string|false
535
	 */
536
	public static function getAppPath($appId) {
537
		if ($appId === null || trim($appId) === '') {
538
			return false;
539
		}
540
541
		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...
542
			return $dir['path'] . '/' . $appId;
543
		}
544
		return false;
545
	}
546
547
548
	/**
549
	 * check if an app's directory is writable
550
	 *
551
	 * @param string $appId
552
	 * @return bool
553
	 */
554
	public static function isAppDirWritable($appId) {
555
		$path = self::getAppPath($appId);
556
		return ($path !== false) ? is_writable($path) : false;
557
	}
558
559
	/**
560
	 * Get the path for the given app on the access
561
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
562
	 *
563
	 * @param string $appId
564
	 * @return string|false
565
	 */
566
	public static function getAppWebPath($appId) {
567
		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...
568
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
569
		}
570
		return false;
571
	}
572
573
	/**
574
	 * get the last version of the app, either from appinfo/version or from appinfo/info.xml
575
	 *
576
	 * @param string $appId
577
	 * @return string
578
	 */
579
	public static function getAppVersion($appId) {
580
		if (!isset(self::$appVersion[$appId])) {
581
			$file = self::getAppPath($appId);
582
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
583
		}
584
		return self::$appVersion[$appId];
585
	}
586
587
	/**
588
	 * get app's version based on it's path
589
	 *
590
	 * @param string $path
591
	 * @return string
592
	 */
593
	public static function getAppVersionByPath($path) {
594
		$versionFile = $path . '/appinfo/version';
595
		$infoFile = $path . '/appinfo/info.xml';
596
		if (is_file($versionFile)) {
597
			return trim(file_get_contents($versionFile));
598
		} else {
599
			$appData = self::getAppInfo($infoFile, true);
600
			return isset($appData['version']) ? $appData['version'] : '';
601
		}
602
	}
603
604
605
	/**
606
	 * Read all app metadata from the info.xml file
607
	 *
608
	 * @param string $appId id of the app or the path of the info.xml file
609
	 * @param boolean $path
610
	 * @param string $lang
611
	 * @return array|null
612
	 * @note all data is read from info.xml, not just pre-defined fields
613
	 */
614
	public static function getAppInfo($appId, $path = false, $lang = null) {
615
		if ($path) {
616
			$file = $appId;
617
		} else {
618
			if ($lang === null && isset(self::$appInfo[$appId])) {
619
				return self::$appInfo[$appId];
620
			}
621
			$appPath = self::getAppPath($appId);
622
			if($appPath === false) {
623
				return null;
624
			}
625
			$file = $appPath . '/appinfo/info.xml';
626
		}
627
628
		$parser = new \OC\App\InfoParser(\OC::$server->getHTTPHelper(), \OC::$server->getURLGenerator());
0 ignored issues
show
Deprecated Code introduced by
The method OC\Server::getHTTPHelper() has been deprecated with message: Use getHTTPClientService()

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...
629
		$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 645 which is incompatible with the return type documented by OC_App::getAppInfo of type array|null.
Loading history...
630
631
		if (is_array($data)) {
632
			$data = OC_App::parseAppInfo($data, $lang);
633
		}
634
		if(isset($data['ocsid'])) {
635
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
636
			if($storedId !== '' && $storedId !== $data['ocsid']) {
637
				$data['ocsid'] = $storedId;
638
			}
639
		}
640
641
		if ($lang === null) {
642
			self::$appInfo[$appId] = $data;
643
		}
644
645
		return $data;
646
	}
647
648
	/**
649
	 * Returns the navigation
650
	 *
651
	 * @return array
652
	 *
653
	 * This function returns an array containing all entries added. The
654
	 * entries are sorted by the key 'order' ascending. Additional to the keys
655
	 * given for each app the following keys exist:
656
	 *   - active: boolean, signals if the user is on this navigation entry
657
	 */
658
	public static function getNavigation() {
659
		$entries = OC::$server->getNavigationManager()->getAll();
660
		$navigation = self::proceedNavigation($entries);
661
		return $navigation;
662
	}
663
664
	/**
665
	 * get the id of loaded app
666
	 *
667
	 * @return string
668
	 */
669
	public static function getCurrentApp() {
670
		$request = \OC::$server->getRequest();
671
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
672
		$topFolder = substr($script, 0, strpos($script, '/'));
673
		if (empty($topFolder)) {
674
			$path_info = $request->getPathInfo();
675
			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...
676
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
677
			}
678
		}
679
		if ($topFolder == 'apps') {
680
			$length = strlen($topFolder);
681
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
682
		} else {
683
			return $topFolder;
684
		}
685
	}
686
687
	/**
688
	 * @param string $type
689
	 * @return array
690
	 */
691
	public static function getForms($type) {
692
		$forms = array();
693
		switch ($type) {
694
			case 'admin':
695
				$source = self::$adminForms;
696
				break;
697
			case 'personal':
698
				$source = self::$personalForms;
699
				break;
700
			default:
701
				return array();
702
		}
703
		foreach ($source as $form) {
704
			$forms[] = include $form;
705
		}
706
		return $forms;
707
	}
708
709
	/**
710
	 * register an admin form to be shown
711
	 *
712
	 * @param string $app
713
	 * @param string $page
714
	 */
715
	public static function registerAdmin($app, $page) {
716
		self::$adminForms[] = $app . '/' . $page . '.php';
717
	}
718
719
	/**
720
	 * register a personal form to be shown
721
	 * @param string $app
722
	 * @param string $page
723
	 */
724
	public static function registerPersonal($app, $page) {
725
		self::$personalForms[] = $app . '/' . $page . '.php';
726
	}
727
728
	/**
729
	 * @param array $entry
730
	 */
731
	public static function registerLogIn(array $entry) {
732
		self::$altLogin[] = $entry;
733
	}
734
735
	/**
736
	 * @return array
737
	 */
738
	public static function getAlternativeLogIns() {
739
		return self::$altLogin;
740
	}
741
742
	/**
743
	 * get a list of all apps in the apps folder
744
	 *
745
	 * @return array an array of app names (string IDs)
746
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
747
	 */
748
	public static function getAllApps() {
749
750
		$apps = array();
751
752
		foreach (OC::$APPSROOTS as $apps_dir) {
753
			if (!is_readable($apps_dir['path'])) {
754
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
755
				continue;
756
			}
757
			$dh = opendir($apps_dir['path']);
758
759
			if (is_resource($dh)) {
760
				while (($file = readdir($dh)) !== false) {
761
762
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
763
764
						$apps[] = $file;
765
					}
766
				}
767
			}
768
		}
769
770
		return $apps;
771
	}
772
773
	/**
774
	 * List all apps, this is used in apps.php
775
	 *
776
	 * @param bool $onlyLocal
777
	 * @param bool $includeUpdateInfo Should we check whether there is an update
778
	 *                                in the app store?
779
	 * @param OCSClient $ocsClient
780
	 * @return array
781
	 */
782
	public static function listAllApps($onlyLocal = false,
783
									   $includeUpdateInfo = true,
784
									   OCSClient $ocsClient) {
785
		$installedApps = OC_App::getAllApps();
786
787
		//TODO which apps do we want to blacklist and how do we integrate
788
		// blacklisting with the multi apps folder feature?
789
790
		//we don't want to show configuration for these
791
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
792
		$appList = array();
793
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
794
795
		foreach ($installedApps as $app) {
796
			if (array_search($app, $blacklist) === false) {
797
798
				$info = OC_App::getAppInfo($app, false, $langCode);
799
				if (!is_array($info)) {
800
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
801
					continue;
802
				}
803
804
				if (!isset($info['name'])) {
805
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
806
					continue;
807
				}
808
809
				$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...
810
				$info['groups'] = null;
811
				if ($enabled === 'yes') {
812
					$active = true;
813
				} else if ($enabled === 'no') {
814
					$active = false;
815
				} else {
816
					$active = true;
817
					$info['groups'] = $enabled;
818
				}
819
820
				$info['active'] = $active;
821
822
				if (self::isShipped($app)) {
823
					$info['internal'] = true;
824
					$info['level'] = self::officialApp;
825
					$info['removable'] = false;
826
				} else {
827
					$info['internal'] = false;
828
					$info['removable'] = true;
829
				}
830
831
				$info['update'] = ($includeUpdateInfo) ? OC_Installer::isUpdateAvailable($app) : null;
832
833
				$appPath = self::getAppPath($app);
834
				if($appPath !== false) {
835
					$appIcon = $appPath . '/img/' . $app . '.svg';
836
					if (file_exists($appIcon)) {
837
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
838
						$info['previewAsIcon'] = true;
839
					} else {
840
						$appIcon = $appPath . '/img/app.svg';
841
						if (file_exists($appIcon)) {
842
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
843
							$info['previewAsIcon'] = true;
844
						}
845
					}
846
				}
847
				$info['version'] = OC_App::getAppVersion($app);
848
				$appList[] = $info;
849
			}
850
		}
851
		if ($onlyLocal) {
852
			$remoteApps = [];
853
		} else {
854
			$remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient);
855
		}
856
		if ($remoteApps) {
857
			// Remove duplicates
858
			foreach ($appList as $app) {
859
				foreach ($remoteApps AS $key => $remote) {
860
					if ($app['name'] === $remote['name'] ||
861
						(isset($app['ocsid']) &&
862
							$app['ocsid'] === $remote['id'])
863
					) {
864
						unset($remoteApps[$key]);
865
					}
866
				}
867
			}
868
			$combinedApps = array_merge($appList, $remoteApps);
869
		} else {
870
			$combinedApps = $appList;
871
		}
872
873
		return $combinedApps;
874
	}
875
876
	/**
877
	 * Returns the internal app ID or false
878
	 * @param string $ocsID
879
	 * @return string|false
880
	 */
881
	public static function getInternalAppIdByOcs($ocsID) {
882
		if(is_numeric($ocsID)) {
883
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
884
			if(array_search($ocsID, $idArray)) {
885
				return array_search($ocsID, $idArray);
886
			}
887
		}
888
		return false;
889
	}
890
891
	/**
892
	 * Get a list of all apps on the appstore
893
	 * @param string $filter
894
	 * @param string|null $category
895
	 * @param OCSClient $ocsClient
896
	 * @return array|bool  multi-dimensional array of apps.
897
	 *                     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
898
	 */
899
	public static function getAppstoreApps($filter = 'approved',
900
										   $category = null,
901
										   OCSClient $ocsClient) {
902
		$categories = [$category];
903
904
		if (is_null($category)) {
905
			$categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion());
906
			if (is_array($categoryNames)) {
907
				// Check that categories of apps were retrieved correctly
908
				if (!$categories = array_keys($categoryNames)) {
909
					return false;
910
				}
911
			} else {
912
				return false;
913
			}
914
		}
915
916
		$page = 0;
917
		$remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion());
918
		$apps = [];
919
		$i = 0;
920
		$l = \OC::$server->getL10N('core');
921
		foreach ($remoteApps as $app) {
922
			$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
923
			// enhance app info (for example the description)
924
			$apps[$i] = OC_App::parseAppInfo($app);
925
			$apps[$i]['author'] = $app['personid'];
926
			$apps[$i]['ocs_id'] = $app['id'];
927
			$apps[$i]['internal'] = 0;
928
			$apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
929
			$apps[$i]['update'] = false;
930
			$apps[$i]['groups'] = false;
931
			$apps[$i]['score'] = $app['score'];
932
			$apps[$i]['removable'] = false;
933
			if ($app['label'] == 'recommended') {
934
				$apps[$i]['internallabel'] = (string)$l->t('Recommended');
935
				$apps[$i]['internalclass'] = 'recommendedapp';
936
			}
937
938
			// Apps from the appstore are always assumed to be compatible with the
939
			// the current release as the initial filtering is done on the appstore
940
			$apps[$i]['dependencies']['owncloud']['@attributes']['min-version'] = implode('.', \OCP\Util::getVersion());
941
			$apps[$i]['dependencies']['owncloud']['@attributes']['max-version'] = implode('.', \OCP\Util::getVersion());
942
943
			$i++;
944
		}
945
946
947
948
		if (empty($apps)) {
949
			return false;
950
		} else {
951
			return $apps;
952
		}
953
	}
954
955
	public static function shouldUpgrade($app) {
956
		$versions = self::getAppVersions();
957
		$currentVersion = OC_App::getAppVersion($app);
958
		if ($currentVersion && isset($versions[$app])) {
959
			$installedVersion = $versions[$app];
960
			if (version_compare($currentVersion, $installedVersion, '>')) {
961
				return true;
962
			}
963
		}
964
		return false;
965
	}
966
967
	/**
968
	 * Adjust the number of version parts of $version1 to match
969
	 * the number of version parts of $version2.
970
	 *
971
	 * @param string $version1 version to adjust
972
	 * @param string $version2 version to take the number of parts from
973
	 * @return string shortened $version1
974
	 */
975
	private static function adjustVersionParts($version1, $version2) {
976
		$version1 = explode('.', $version1);
977
		$version2 = explode('.', $version2);
978
		// reduce $version1 to match the number of parts in $version2
979
		while (count($version1) > count($version2)) {
980
			array_pop($version1);
981
		}
982
		// if $version1 does not have enough parts, add some
983
		while (count($version1) < count($version2)) {
984
			$version1[] = '0';
985
		}
986
		return implode('.', $version1);
987
	}
988
989
	/**
990
	 * Check whether the current ownCloud version matches the given
991
	 * application's version requirements.
992
	 *
993
	 * The comparison is made based on the number of parts that the
994
	 * app info version has. For example for ownCloud 6.0.3 if the
995
	 * app info version is expecting version 6.0, the comparison is
996
	 * made on the first two parts of the ownCloud version.
997
	 * This means that it's possible to specify "requiremin" => 6
998
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
999
	 *
1000
	 * @param string $ocVersion ownCloud version to check against
1001
	 * @param array $appInfo app info (from xml)
1002
	 *
1003
	 * @return boolean true if compatible, otherwise false
1004
	 */
1005
	public static function isAppCompatible($ocVersion, $appInfo) {
1006
		$requireMin = '';
1007
		$requireMax = '';
1008
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
1009
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1010
		} else if (isset($appInfo['requiremin'])) {
1011
			$requireMin = $appInfo['requiremin'];
1012
		} else if (isset($appInfo['require'])) {
1013
			$requireMin = $appInfo['require'];
1014
		}
1015
1016
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
1017
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1018
		} else if (isset($appInfo['requiremax'])) {
1019
			$requireMax = $appInfo['requiremax'];
1020
		}
1021
1022
		if (is_array($ocVersion)) {
1023
			$ocVersion = implode('.', $ocVersion);
1024
		}
1025
1026 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...
1027
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1028
		) {
1029
1030
			return false;
1031
		}
1032
1033 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...
1034
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1035
		) {
1036
1037
			return false;
1038
		}
1039
1040
		return true;
1041
	}
1042
1043
	/**
1044
	 * get the installed version of all apps
1045
	 */
1046
	public static function getAppVersions() {
1047
		static $versions;
1048
1049
		if(!$versions) {
1050
			$appConfig = \OC::$server->getAppConfig();
1051
			$versions = $appConfig->getValues(false, 'installed_version');
1052
		}
1053
		return $versions;
1054
	}
1055
1056
1057
	/**
1058
	 * @param string $app
1059
	 * @return bool
1060
	 * @throws Exception if app is not compatible with this version of ownCloud
1061
	 * @throws Exception if no app-name was specified
1062
	 */
1063
	public static function installApp($app) {
1064
		$l = \OC::$server->getL10N('core');
1065
		$config = \OC::$server->getConfig();
1066
		$ocsClient = new OCSClient(
1067
			\OC::$server->getHTTPClientService(),
1068
			$config,
1069
			\OC::$server->getLogger()
1070
		);
1071
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
1072
1073
		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
1074
		if (!is_numeric($app)) {
1075
			$shippedVersion = self::getAppVersion($app);
1076
			if ($appData && version_compare($shippedVersion, $appData['version'], '<')) {
1077
				$app = self::downloadApp($app);
1078
			} else {
1079
				$app = OC_Installer::installShippedApp($app);
1080
			}
1081
		} else {
1082
			// Maybe the app is already installed - compare the version in this
1083
			// case and use the local already installed one.
1084
			// FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me.
1085
			$internalAppId = self::getInternalAppIdByOcs($app);
1086
			if($internalAppId !== false) {
1087
				if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) {
1088
					$app = self::downloadApp($app);
1089
				} else {
1090
					self::enable($internalAppId);
1091
					$app = $internalAppId;
1092
				}
1093
			} else {
1094
				$app = self::downloadApp($app);
1095
			}
1096
		}
1097
1098
		if ($app !== false) {
1099
			// check if the app is compatible with this version of ownCloud
1100
			$info = self::getAppInfo($app);
1101
			if(!is_array($info)) {
1102
				throw new \Exception(
1103
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1104
						[$info['name']]
1105
					)
1106
				);
1107
			}
1108
1109
			$version = \OCP\Util::getVersion();
1110
			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...
1111
				throw new \Exception(
1112
					$l->t('App "%s" cannot be installed because it is not compatible with this version of Nextcloud.',
1113
						array($info['name'])
1114
					)
1115
				);
1116
			}
1117
1118
			// check for required dependencies
1119
			$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1120
			$missing = $dependencyAnalyzer->analyze($info);
1121
			if (!empty($missing)) {
1122
				$missingMsg = join(PHP_EOL, $missing);
1123
				throw new \Exception(
1124
					$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1125
						array($info['name'], $missingMsg)
1126
					)
1127
				);
1128
			}
1129
1130
			$config->setAppValue($app, 'enabled', 'yes');
1131
			if (isset($appData['id'])) {
1132
				$config->setAppValue($app, 'ocsid', $appData['id']);
1133
			}
1134
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1135
		} else {
1136
			throw new \Exception($l->t("No app name specified"));
1137
		}
1138
1139
		return $app;
1140
	}
1141
1142
	/**
1143
	 * update the database for the app and call the update script
1144
	 *
1145
	 * @param string $appId
1146
	 * @return bool
1147
	 */
1148
	public static function updateApp($appId) {
1149
		$appPath = self::getAppPath($appId);
1150
		if($appPath === false) {
1151
			return false;
1152
		}
1153
		if (file_exists($appPath . '/appinfo/database.xml')) {
1154
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1155
		}
1156
		unset(self::$appVersion[$appId]);
1157
		// run upgrade code
1158
		if (file_exists($appPath . '/appinfo/update.php')) {
1159
			self::loadApp($appId, false);
1160
			include $appPath . '/appinfo/update.php';
1161
		}
1162
1163
		//set remote/public handlers
1164
		$appData = self::getAppInfo($appId);
1165
		if (array_key_exists('ocsid', $appData)) {
1166
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1167 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...
1168
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1169
		}
1170
		foreach ($appData['remote'] as $name => $path) {
1171
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1172
		}
1173
		foreach ($appData['public'] as $name => $path) {
1174
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1175
		}
1176
1177
		self::setAppTypes($appId);
1178
1179
		$version = \OC_App::getAppVersion($appId);
1180
		\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...
1181
1182
		return true;
1183
	}
1184
1185
	/**
1186
	 * @param string $appId
1187
	 * @return \OC\Files\View|false
1188
	 */
1189
	public static function getStorage($appId) {
1190
		if (OC_App::isEnabled($appId)) { //sanity check
1191
			if (OC_User::isLoggedIn()) {
1192
				$view = new \OC\Files\View('/' . OC_User::getUser());
1193
				if (!$view->file_exists($appId)) {
1194
					$view->mkdir($appId);
1195
				}
1196
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1197
			} else {
1198
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1199
				return false;
1200
			}
1201
		} else {
1202
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1203
			return false;
1204
		}
1205
	}
1206
1207
	protected static function findBestL10NOption($options, $lang) {
1208
		$fallback = $similarLangFallback = $englishFallback = false;
1209
1210
		$lang = strtolower($lang);
1211
		$similarLang = $lang;
1212
		if (strpos($similarLang, '_')) {
1213
			// For "de_DE" we want to find "de" and the other way around
1214
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1215
		}
1216
1217
		foreach ($options as $option) {
1218
			if (is_array($option)) {
1219
				if ($fallback === false) {
1220
					$fallback = $option['@value'];
1221
				}
1222
1223
				if (!isset($option['@attributes']['lang'])) {
1224
					continue;
1225
				}
1226
1227
				$attributeLang = strtolower($option['@attributes']['lang']);
1228
				if ($attributeLang === $lang) {
1229
					return $option['@value'];
1230
				}
1231
1232
				if ($attributeLang === $similarLang) {
1233
					$similarLangFallback = $option['@value'];
1234
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1235
					if ($similarLangFallback === false) {
1236
						$similarLangFallback =  $option['@value'];
1237
					}
1238
				}
1239
			} else {
1240
				$englishFallback = $option;
1241
			}
1242
		}
1243
1244
		if ($similarLangFallback !== false) {
1245
			return $similarLangFallback;
1246
		} else if ($englishFallback !== false) {
1247
			return $englishFallback;
1248
		}
1249
		return (string) $fallback;
1250
	}
1251
1252
	/**
1253
	 * parses the app data array and enhanced the 'description' value
1254
	 *
1255
	 * @param array $data the app data
1256
	 * @param string $lang
1257
	 * @return array improved app data
1258
	 */
1259
	public static function parseAppInfo(array $data, $lang = null) {
1260
1261 View Code Duplication
		if ($lang && isset($data['name']) && is_array($data['name'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
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...
1262
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1263
		}
1264 View Code Duplication
		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
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...
1265
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1266
		}
1267 View Code Duplication
		if ($lang && isset($data['description']) && is_array($data['description'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
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...
1268
			$data['description'] = self::findBestL10NOption($data['description'], $lang);
1269
		}
1270
1271
		// just modify the description if it is available
1272
		// otherwise this will create a $data element with an empty 'description'
1273
		if (isset($data['description'])) {
1274
			if (is_string($data['description'])) {
1275
				// sometimes the description contains line breaks and they are then also
1276
				// shown in this way in the app management which isn't wanted as HTML
1277
				// manages line breaks itself
1278
1279
				// first of all we split on empty lines
1280
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1281
1282
				$result = [];
1283
				foreach ($paragraphs as $value) {
1284
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1285
					// with a single space - also trims whitespace
1286
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1287
				}
1288
1289
				// join the single paragraphs with a empty line in between
1290
				$data['description'] = implode("\n\n", $result);
1291
1292
			} else {
1293
				$data['description'] = '';
1294
			}
1295
		}
1296
1297
		return $data;
1298
	}
1299
}
1300