Completed
Push — master ( 5412c2...2d62f9 )
by Blizzz
45:23 queued 29:26
created

OC_App::getAppWebPath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2016, Lukas Reschke <[email protected]>
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Björn Schießle <[email protected]>
10
 * @author Borjan Tchakaloff <[email protected]>
11
 * @author Brice Maron <[email protected]>
12
 * @author Christopher Schäpers <[email protected]>
13
 * @author Felix Moeller <[email protected]>
14
 * @author Frank Karlitschek <[email protected]>
15
 * @author Georg Ehrke <[email protected]>
16
 * @author Jakob Sack <[email protected]>
17
 * @author Jan-Christoph Borchardt <[email protected]>
18
 * @author Joas Schilling <[email protected]>
19
 * @author Jörn Friedrich Dreyer <[email protected]>
20
 * @author Kamil Domanski <[email protected]>
21
 * @author Klaas Freitag <[email protected]>
22
 * @author Lukas Reschke <[email protected]>
23
 * @author Markus Goetz <[email protected]>
24
 * @author Morris Jobke <[email protected]>
25
 * @author RealRancor <[email protected]>
26
 * @author Robin Appelman <[email protected]>
27
 * @author Robin McCorkell <[email protected]>
28
 * @author Roeland Jago Douma <[email protected]>
29
 * @author Sam Tuke <[email protected]>
30
 * @author Thomas Müller <[email protected]>
31
 * @author Thomas Tanghus <[email protected]>
32
 * @author Tom Needham <[email protected]>
33
 * @author Vincent Petry <[email protected]>
34
 *
35
 * @license AGPL-3.0
36
 *
37
 * This code is free software: you can redistribute it and/or modify
38
 * it under the terms of the GNU Affero General Public License, version 3,
39
 * as published by the Free Software Foundation.
40
 *
41
 * This program is distributed in the hope that it will be useful,
42
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44
 * GNU Affero General Public License for more details.
45
 *
46
 * You should have received a copy of the GNU Affero General Public License, version 3,
47
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
48
 *
49
 */
50
use OC\App\DependencyAnalyzer;
51
use OC\App\InfoParser;
52
use OC\App\Platform;
53
use OC\DB\MigrationService;
54
use OC\Installer;
55
use OC\Repair;
56
use OCP\App\ManagerEvent;
57
58
/**
59
 * This class manages the apps. It allows them to register and integrate in the
60
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
61
 * upgrading and removing apps.
62
 */
63
class OC_App {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
64
	static private $appVersion = [];
65
	static private $adminForms = array();
66
	static private $personalForms = array();
67
	static private $appInfo = array();
68
	static private $appTypes = array();
69
	static private $loadedApps = array();
70
	static private $altLogin = array();
71
	static private $alreadyRegistered = [];
72
	const officialApp = 200;
73
74
	/**
75
	 * clean the appId
76
	 *
77
	 * @param string|boolean $app AppId that needs to be cleaned
78
	 * @return string
79
	 */
80
	public static function cleanAppId($app) {
81
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
82
	}
83
84
	/**
85
	 * Check if an app is loaded
86
	 *
87
	 * @param string $app
88
	 * @return bool
89
	 */
90
	public static function isAppLoaded($app) {
91
		return in_array($app, self::$loadedApps, true);
92
	}
93
94
	/**
95
	 * loads all apps
96
	 *
97
	 * @param string[] | string | null $types
98
	 * @return bool
99
	 *
100
	 * This function walks through the ownCloud directory and loads all apps
101
	 * it can find. A directory contains an app if the file /appinfo/info.xml
102
	 * exists.
103
	 *
104
	 * if $types is set, only apps of those types will be loaded
105
	 */
106
	public static function loadApps($types = null) {
107
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
108
			return false;
109
		}
110
		// Load the enabled apps here
111
		$apps = self::getEnabledApps();
112
113
		// Add each apps' folder as allowed class path
114
		foreach($apps as $app) {
115
			$path = self::getAppPath($app);
116
			if($path !== false) {
117
				self::registerAutoloading($app, $path);
118
			}
119
		}
120
121
		// prevent app.php from printing output
122
		ob_start();
123
		foreach ($apps as $app) {
124
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
125
				self::loadApp($app);
126
			}
127
		}
128
		ob_end_clean();
129
130
		return true;
131
	}
132
133
	/**
134
	 * load a single app
135
	 *
136
	 * @param string $app
137
	 */
138
	public static function loadApp($app) {
139
		self::$loadedApps[] = $app;
140
		$appPath = self::getAppPath($app);
141
		if($appPath === false) {
142
			return;
143
		}
144
145
		// in case someone calls loadApp() directly
146
		self::registerAutoloading($app, $appPath);
147
148
		if (is_file($appPath . '/appinfo/app.php')) {
149
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
150
			self::requireAppFile($app);
151
			if (self::isType($app, array('authentication'))) {
152
				// since authentication apps affect the "is app enabled for group" check,
153
				// the enabled apps cache needs to be cleared to make sure that the
154
				// next time getEnableApps() is called it will also include apps that were
155
				// enabled for groups
156
				self::$enabledAppsCache = array();
157
			}
158
			\OC::$server->getEventLogger()->end('load_app_' . $app);
159
		}
160
161
		$info = self::getAppInfo($app);
162 View Code Duplication
		if (!empty($info['activity']['filters'])) {
163
			foreach ($info['activity']['filters'] as $filter) {
164
				\OC::$server->getActivityManager()->registerFilter($filter);
165
			}
166
		}
167 View Code Duplication
		if (!empty($info['activity']['settings'])) {
168
			foreach ($info['activity']['settings'] as $setting) {
169
				\OC::$server->getActivityManager()->registerSetting($setting);
170
			}
171
		}
172 View Code Duplication
		if (!empty($info['activity']['providers'])) {
173
			foreach ($info['activity']['providers'] as $provider) {
174
				\OC::$server->getActivityManager()->registerProvider($provider);
175
			}
176
		}
177
		if (!empty($info['collaboration']['plugins'])) {
178
			// deal with one or many plugin entries
179
			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
180
				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
181
			foreach ($plugins as $plugin) {
182
				if($plugin['@attributes']['type'] === 'collaborator-search') {
183
					$pluginInfo = [
184
						'shareType' => $plugin['@attributes']['share-type'],
185
						'class' => $plugin['@value'],
186
					];
187
					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
188
				}
189
			}
190
		}
191
	}
192
193
	/**
194
	 * @internal
195
	 * @param string $app
196
	 * @param string $path
197
	 */
198
	public static function registerAutoloading($app, $path) {
199
		$key = $app . '-' . $path;
200
		if(isset(self::$alreadyRegistered[$key])) {
201
			return;
202
		}
203
		self::$alreadyRegistered[$key] = true;
204
		// Register on PSR-4 composer autoloader
205
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
206
		\OC::$server->registerNamespace($app, $appNamespace);
207
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
208
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
209
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
210
		}
211
212
		// Register on legacy autoloader
213
		\OC::$loader->addValidRoot($path);
214
	}
215
216
	/**
217
	 * Load app.php from the given app
218
	 *
219
	 * @param string $app app name
220
	 */
221
	private static function requireAppFile($app) {
222
		try {
223
			// encapsulated here to avoid variable scope conflicts
224
			require_once $app . '/appinfo/app.php';
225
		} catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
226
			\OC::$server->getLogger()->logException($ex);
227
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
228
			if (!in_array($app, $blacklist)) {
229
				self::disable($app);
230
			}
231
		}
232
	}
233
234
	/**
235
	 * check if an app is of a specific type
236
	 *
237
	 * @param string $app
238
	 * @param string|array $types
239
	 * @return bool
240
	 */
241
	public static function isType($app, $types) {
242
		if (is_string($types)) {
243
			$types = array($types);
244
		}
245
		$appTypes = self::getAppTypes($app);
246
		foreach ($types as $type) {
247
			if (array_search($type, $appTypes) !== false) {
248
				return true;
249
			}
250
		}
251
		return false;
252
	}
253
254
	/**
255
	 * get the types of an app
256
	 *
257
	 * @param string $app
258
	 * @return array
259
	 */
260
	private static function getAppTypes($app) {
261
		//load the cache
262
		if (count(self::$appTypes) == 0) {
263
			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...
264
		}
265
266
		if (isset(self::$appTypes[$app])) {
267
			return explode(',', self::$appTypes[$app]);
268
		} else {
269
			return array();
270
		}
271
	}
272
273
	/**
274
	 * read app types from info.xml and cache them in the database
275
	 */
276
	public static function setAppTypes($app) {
277
		$appData = self::getAppInfo($app);
278
		if(!is_array($appData)) {
279
			return;
280
		}
281
282
		if (isset($appData['types'])) {
283
			$appTypes = implode(',', $appData['types']);
284
		} else {
285
			$appTypes = '';
286
			$appData['types'] = [];
287
		}
288
289
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
290
291
		if (\OC::$server->getAppManager()->hasProtectedAppType($appData['types'])) {
292
			$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'yes');
293
			if ($enabled !== 'yes' && $enabled !== 'no') {
294
				\OC::$server->getAppConfig()->setValue($app, 'enabled', 'yes');
295
			}
296
		}
297
	}
298
299
	/**
300
	 * get all enabled apps
301
	 */
302
	protected static $enabledAppsCache = array();
303
304
	/**
305
	 * Returns apps enabled for the current user.
306
	 *
307
	 * @param bool $forceRefresh whether to refresh the cache
308
	 * @param bool $all whether to return apps for all users, not only the
309
	 * currently logged in one
310
	 * @return string[]
311
	 */
312
	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...
313
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
314
			return array();
315
		}
316
		// in incognito mode or when logged out, $user will be false,
317
		// which is also the case during an upgrade
318
		$appManager = \OC::$server->getAppManager();
319
		if ($all) {
320
			$user = null;
321
		} else {
322
			$user = \OC::$server->getUserSession()->getUser();
323
		}
324
325
		if (is_null($user)) {
326
			$apps = $appManager->getInstalledApps();
327
		} else {
328
			$apps = $appManager->getEnabledAppsForUser($user);
329
		}
330
		$apps = array_filter($apps, function ($app) {
331
			return $app !== 'files';//we add this manually
332
		});
333
		sort($apps);
334
		array_unshift($apps, 'files');
335
		return $apps;
336
	}
337
338
	/**
339
	 * checks whether or not an app is enabled
340
	 *
341
	 * @param string $app app
342
	 * @return bool
343
	 *
344
	 * This function checks whether or not an app is enabled.
345
	 */
346
	public static function isEnabled($app) {
347
		return \OC::$server->getAppManager()->isEnabledForUser($app);
348
	}
349
350
	/**
351
	 * enables an app
352
	 *
353
	 * @param string $appId
354
	 * @param array $groups (optional) when set, only these groups will have access to the app
355
	 * @throws \Exception
356
	 * @return void
357
	 *
358
	 * This function set an app as enabled in appconfig.
359
	 */
360
	public function enable($appId,
361
						   $groups = null) {
362
		self::$enabledAppsCache = []; // flush
363
364
		// Check if app is already downloaded
365
		$installer = new Installer(
366
			\OC::$server->getAppFetcher(),
367
			\OC::$server->getHTTPClientService(),
368
			\OC::$server->getTempManager(),
369
			\OC::$server->getLogger(),
370
			\OC::$server->getConfig()
371
		);
372
		$isDownloaded = $installer->isDownloaded($appId);
373
374
		if(!$isDownloaded) {
375
			$installer->downloadApp($appId);
376
		}
377
378
		$installer->installApp($appId);
379
380
		$appManager = \OC::$server->getAppManager();
381
		if (!is_null($groups)) {
382
			$groupManager = \OC::$server->getGroupManager();
383
			$groupsList = [];
384
			foreach ($groups as $group) {
385
				$groupItem = $groupManager->get($group);
386
				if ($groupItem instanceof \OCP\IGroup) {
387
					$groupsList[] = $groupManager->get($group);
388
				}
389
			}
390
			$appManager->enableAppForGroups($appId, $groupsList);
391
		} else {
392
			$appManager->enableApp($appId);
393
		}
394
	}
395
396
	/**
397
	 * @param string $app
398
	 * @return bool
399
	 */
400
	public static function removeApp($app) {
401
		if (\OC::$server->getAppManager()->isShipped($app)) {
402
			return false;
403
		}
404
405
		$installer = new Installer(
406
			\OC::$server->getAppFetcher(),
407
			\OC::$server->getHTTPClientService(),
408
			\OC::$server->getTempManager(),
409
			\OC::$server->getLogger(),
410
			\OC::$server->getConfig()
411
		);
412
		return $installer->removeApp($app);
413
	}
414
415
	/**
416
	 * This function set an app as disabled in appconfig.
417
	 *
418
	 * @param string $app app
419
	 * @throws Exception
420
	 */
421
	public static function disable($app) {
422
		// flush
423
		self::$enabledAppsCache = array();
424
425
		// run uninstall steps
426
		$appData = OC_App::getAppInfo($app);
427
		if (!is_null($appData)) {
428
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
429
		}
430
431
		// emit disable hook - needed anymore ?
432
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
433
434
		// finally disable it
435
		$appManager = \OC::$server->getAppManager();
436
		$appManager->disableApp($app);
437
	}
438
439
	// This is private as well. It simply works, so don't ask for more details
440
	private static function proceedNavigation($list) {
441
		usort($list, function($a, $b) {
442
			if (isset($a['order']) && isset($b['order'])) {
443
				return ($a['order'] < $b['order']) ? -1 : 1;
444
			} else if (isset($a['order']) || isset($b['order'])) {
445
				return isset($a['order']) ? -1 : 1;
446
			} else {
447
				return ($a['name'] < $b['name']) ? -1 : 1;
448
			}
449
		});
450
451
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
452
		foreach ($list as $index => &$navEntry) {
453
			if ($navEntry['id'] == $activeApp) {
454
				$navEntry['active'] = true;
455
			} else {
456
				$navEntry['active'] = false;
457
			}
458
		}
459
		unset($navEntry);
460
461
		return $list;
462
	}
463
464
	/**
465
	 * Get the path where to install apps
466
	 *
467
	 * @return string|false
468
	 */
469
	public static function getInstallPath() {
470
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
471
			return false;
472
		}
473
474
		foreach (OC::$APPSROOTS as $dir) {
475
			if (isset($dir['writable']) && $dir['writable'] === true) {
476
				return $dir['path'];
477
			}
478
		}
479
480
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
481
		return null;
482
	}
483
484
485
	/**
486
	 * search for an app in all app-directories
487
	 *
488
	 * @param string $appId
489
	 * @return false|string
490
	 */
491
	public static function findAppInDirectories($appId) {
492
		$sanitizedAppId = self::cleanAppId($appId);
493
		if($sanitizedAppId !== $appId) {
494
			return false;
495
		}
496
		static $app_dir = array();
497
498
		if (isset($app_dir[$appId])) {
499
			return $app_dir[$appId];
500
		}
501
502
		$possibleApps = array();
503
		foreach (OC::$APPSROOTS as $dir) {
504
			if (file_exists($dir['path'] . '/' . $appId)) {
505
				$possibleApps[] = $dir;
506
			}
507
		}
508
509
		if (empty($possibleApps)) {
510
			return false;
511
		} elseif (count($possibleApps) === 1) {
512
			$dir = array_shift($possibleApps);
513
			$app_dir[$appId] = $dir;
514
			return $dir;
515
		} else {
516
			$versionToLoad = array();
517
			foreach ($possibleApps as $possibleApp) {
518
				$version = self::getAppVersionByPath($possibleApp['path']);
519
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
520
					$versionToLoad = array(
521
						'dir' => $possibleApp,
522
						'version' => $version,
523
					);
524
				}
525
			}
526
			$app_dir[$appId] = $versionToLoad['dir'];
527
			return $versionToLoad['dir'];
528
			//TODO - write test
529
		}
530
	}
531
532
	/**
533
	 * Get the directory for the given app.
534
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
535
	 *
536
	 * @param string $appId
537
	 * @return string|false
538
	 */
539
	public static function getAppPath($appId) {
540
		if ($appId === null || trim($appId) === '') {
541
			return false;
542
		}
543
544
		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...
545
			return $dir['path'] . '/' . $appId;
546
		}
547
		return 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
	public static function getAppWebPath($appId) {
558
		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
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
560
		}
561
		return false;
562
	}
563
564
	/**
565
	 * get the last version of the app from appinfo/info.xml
566
	 *
567
	 * @param string $appId
568
	 * @param bool $useCache
569
	 * @return string
570
	 */
571
	public static function getAppVersion($appId, $useCache = true) {
572
		if($useCache && isset(self::$appVersion[$appId])) {
573
			return self::$appVersion[$appId];
574
		}
575
576
		$file = self::getAppPath($appId);
577
		self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
578
		return self::$appVersion[$appId];
579
	}
580
581
	/**
582
	 * get app's version based on it's path
583
	 *
584
	 * @param string $path
585
	 * @return string
586
	 */
587
	public static function getAppVersionByPath($path) {
588
		$infoFile = $path . '/appinfo/info.xml';
589
		$appData = self::getAppInfo($infoFile, true);
590
		return isset($appData['version']) ? $appData['version'] : '';
591
	}
592
593
594
	/**
595
	 * Read all app metadata from the info.xml file
596
	 *
597
	 * @param string $appId id of the app or the path of the info.xml file
598
	 * @param bool $path
599
	 * @param string $lang
600
	 * @return array|null
601
	 * @note all data is read from info.xml, not just pre-defined fields
602
	 */
603
	public static function getAppInfo($appId, $path = false, $lang = null) {
604
		if ($path) {
605
			$file = $appId;
606
		} else {
607
			if ($lang === null && isset(self::$appInfo[$appId])) {
608
				return self::$appInfo[$appId];
609
			}
610
			$appPath = self::getAppPath($appId);
611
			if($appPath === false) {
612
				return null;
613
			}
614
			$file = $appPath . '/appinfo/info.xml';
615
		}
616
617
		$parser = new InfoParser(\OC::$server->getMemCacheFactory()->createLocal('core.appinfo'));
618
		$data = $parser->parse($file);
619
620
		if (is_array($data)) {
621
			$data = OC_App::parseAppInfo($data, $lang);
622
		}
623
		if(isset($data['ocsid'])) {
624
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
625
			if($storedId !== '' && $storedId !== $data['ocsid']) {
626
				$data['ocsid'] = $storedId;
627
			}
628
		}
629
630
		if ($lang === null) {
631
			self::$appInfo[$appId] = $data;
632
		}
633
634
		return $data;
635
	}
636
637
	/**
638
	 * Returns the navigation
639
	 *
640
	 * @return array
641
	 *
642
	 * This function returns an array containing all entries added. The
643
	 * entries are sorted by the key 'order' ascending. Additional to the keys
644
	 * given for each app the following keys exist:
645
	 *   - active: boolean, signals if the user is on this navigation entry
646
	 */
647
	public static function getNavigation() {
648
		$entries = OC::$server->getNavigationManager()->getAll();
649
		return self::proceedNavigation($entries);
650
	}
651
652
	/**
653
	 * Returns the Settings Navigation
654
	 *
655
	 * @return string[]
656
	 *
657
	 * This function returns an array containing all settings pages added. The
658
	 * entries are sorted by the key 'order' ascending.
659
	 */
660
	public static function getSettingsNavigation() {
661
		$entries = OC::$server->getNavigationManager()->getAll('settings');
662
		return self::proceedNavigation($entries);
663
	}
664
665
	/**
666
	 * get the id of loaded app
667
	 *
668
	 * @return string
669
	 */
670
	public static function getCurrentApp() {
671
		$request = \OC::$server->getRequest();
672
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
673
		$topFolder = substr($script, 0, strpos($script, '/'));
674
		if (empty($topFolder)) {
675
			$path_info = $request->getPathInfo();
676
			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...
677
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
678
			}
679
		}
680
		if ($topFolder == 'apps') {
681
			$length = strlen($topFolder);
682
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
683
		} else {
684
			return $topFolder;
685
		}
686
	}
687
688
	/**
689
	 * @param string $type
690
	 * @return array
691
	 */
692
	public static function getForms($type) {
693
		$forms = array();
694
		switch ($type) {
695
			case 'admin':
696
				$source = self::$adminForms;
697
				break;
698
			case 'personal':
699
				$source = self::$personalForms;
700
				break;
701
			default:
702
				return array();
703
		}
704
		foreach ($source as $form) {
705
			$forms[] = include $form;
706
		}
707
		return $forms;
708
	}
709
710
	/**
711
	 * register an admin form to be shown
712
	 *
713
	 * @param string $app
714
	 * @param string $page
715
	 */
716
	public static function registerAdmin($app, $page) {
717
		self::$adminForms[] = $app . '/' . $page . '.php';
718
	}
719
720
	/**
721
	 * register a personal form to be shown
722
	 * @param string $app
723
	 * @param string $page
724
	 */
725
	public static function registerPersonal($app, $page) {
726
		self::$personalForms[] = $app . '/' . $page . '.php';
727
	}
728
729
	/**
730
	 * @param array $entry
731
	 */
732
	public static function registerLogIn(array $entry) {
733
		self::$altLogin[] = $entry;
734
	}
735
736
	/**
737
	 * @return array
738
	 */
739
	public static function getAlternativeLogIns() {
740
		return self::$altLogin;
741
	}
742
743
	/**
744
	 * get a list of all apps in the apps folder
745
	 *
746
	 * @return array an array of app names (string IDs)
747
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
748
	 */
749
	public static function getAllApps() {
750
751
		$apps = array();
752
753
		foreach (OC::$APPSROOTS as $apps_dir) {
754
			if (!is_readable($apps_dir['path'])) {
755
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
756
				continue;
757
			}
758
			$dh = opendir($apps_dir['path']);
759
760
			if (is_resource($dh)) {
761
				while (($file = readdir($dh)) !== false) {
762
763
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
764
765
						$apps[] = $file;
766
					}
767
				}
768
			}
769
		}
770
771
		return $apps;
772
	}
773
774
	/**
775
	 * List all apps, this is used in apps.php
776
	 *
777
	 * @return array
778
	 */
779
	public function listAllApps() {
780
		$installedApps = OC_App::getAllApps();
781
782
		$appManager = \OC::$server->getAppManager();
783
		//we don't want to show configuration for these
784
		$blacklist = $appManager->getAlwaysEnabledApps();
785
		$appList = array();
786
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
787
		$urlGenerator = \OC::$server->getURLGenerator();
788
789
		foreach ($installedApps as $app) {
790
			if (array_search($app, $blacklist) === false) {
791
792
				$info = OC_App::getAppInfo($app, false, $langCode);
793
				if (!is_array($info)) {
794
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
795
					continue;
796
				}
797
798
				if (!isset($info['name'])) {
799
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
800
					continue;
801
				}
802
803
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
804
				$info['groups'] = null;
805
				if ($enabled === 'yes') {
806
					$active = true;
807
				} else if ($enabled === 'no') {
808
					$active = false;
809
				} else {
810
					$active = true;
811
					$info['groups'] = $enabled;
812
				}
813
814
				$info['active'] = $active;
815
816
				if ($appManager->isShipped($app)) {
817
					$info['internal'] = true;
818
					$info['level'] = self::officialApp;
819
					$info['removable'] = false;
820
				} else {
821
					$info['internal'] = false;
822
					$info['removable'] = true;
823
				}
824
825
				$appPath = self::getAppPath($app);
826
				if($appPath !== false) {
827
					$appIcon = $appPath . '/img/' . $app . '.svg';
828
					if (file_exists($appIcon)) {
829
						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
830
						$info['previewAsIcon'] = true;
831
					} else {
832
						$appIcon = $appPath . '/img/app.svg';
833
						if (file_exists($appIcon)) {
834
							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
835
							$info['previewAsIcon'] = true;
836
						}
837
					}
838
				}
839
				// fix documentation
840
				if (isset($info['documentation']) && is_array($info['documentation'])) {
841
					foreach ($info['documentation'] as $key => $url) {
842
						// If it is not an absolute URL we assume it is a key
843
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
844
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
845
							$url = $urlGenerator->linkToDocs($url);
846
						}
847
848
						$info['documentation'][$key] = $url;
849
					}
850
				}
851
852
				$info['version'] = OC_App::getAppVersion($app);
853
				$appList[] = $info;
854
			}
855
		}
856
857
		return $appList;
858
	}
859
860
	/**
861
	 * Returns the internal app ID or false
862
	 * @param string $ocsID
863
	 * @return string|false
864
	 */
865
	public static function getInternalAppIdByOcs($ocsID) {
866
		if(is_numeric($ocsID)) {
867
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
868
			if(array_search($ocsID, $idArray)) {
869
				return array_search($ocsID, $idArray);
870
			}
871
		}
872
		return false;
873
	}
874
875
	public static function shouldUpgrade($app) {
876
		$versions = self::getAppVersions();
877
		$currentVersion = OC_App::getAppVersion($app);
878
		if ($currentVersion && isset($versions[$app])) {
879
			$installedVersion = $versions[$app];
880
			if (!version_compare($currentVersion, $installedVersion, '=')) {
881
				return true;
882
			}
883
		}
884
		return false;
885
	}
886
887
	/**
888
	 * Adjust the number of version parts of $version1 to match
889
	 * the number of version parts of $version2.
890
	 *
891
	 * @param string $version1 version to adjust
892
	 * @param string $version2 version to take the number of parts from
893
	 * @return string shortened $version1
894
	 */
895
	private static function adjustVersionParts($version1, $version2) {
896
		$version1 = explode('.', $version1);
897
		$version2 = explode('.', $version2);
898
		// reduce $version1 to match the number of parts in $version2
899
		while (count($version1) > count($version2)) {
900
			array_pop($version1);
901
		}
902
		// if $version1 does not have enough parts, add some
903
		while (count($version1) < count($version2)) {
904
			$version1[] = '0';
905
		}
906
		return implode('.', $version1);
907
	}
908
909
	/**
910
	 * Check whether the current ownCloud version matches the given
911
	 * application's version requirements.
912
	 *
913
	 * The comparison is made based on the number of parts that the
914
	 * app info version has. For example for ownCloud 6.0.3 if the
915
	 * app info version is expecting version 6.0, the comparison is
916
	 * made on the first two parts of the ownCloud version.
917
	 * This means that it's possible to specify "requiremin" => 6
918
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
919
	 *
920
	 * @param string $ocVersion ownCloud version to check against
921
	 * @param array $appInfo app info (from xml)
922
	 *
923
	 * @return boolean true if compatible, otherwise false
924
	 */
925
	public static function isAppCompatible($ocVersion, $appInfo) {
926
		$requireMin = '';
927
		$requireMax = '';
928
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
929
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
930 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
931
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
932
		} else if (isset($appInfo['requiremin'])) {
933
			$requireMin = $appInfo['requiremin'];
934
		} else if (isset($appInfo['require'])) {
935
			$requireMin = $appInfo['require'];
936
		}
937
938
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
939
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
940 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
941
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
942
		} else if (isset($appInfo['requiremax'])) {
943
			$requireMax = $appInfo['requiremax'];
944
		}
945
946
		if (is_array($ocVersion)) {
947
			$ocVersion = implode('.', $ocVersion);
948
		}
949
950 View Code Duplication
		if (!empty($requireMin)
951
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
952
		) {
953
954
			return false;
955
		}
956
957 View Code Duplication
		if (!empty($requireMax)
958
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
959
		) {
960
			return false;
961
		}
962
963
		return true;
964
	}
965
966
	/**
967
	 * get the installed version of all apps
968
	 */
969
	public static function getAppVersions() {
970
		static $versions;
971
972
		if(!$versions) {
973
			$appConfig = \OC::$server->getAppConfig();
974
			$versions = $appConfig->getValues(false, 'installed_version');
975
		}
976
		return $versions;
977
	}
978
979
	/**
980
	 * @param string $app
981
	 * @param \OCP\IConfig $config
982
	 * @param \OCP\IL10N $l
983
	 * @return bool
984
	 *
985
	 * @throws Exception if app is not compatible with this version of ownCloud
986
	 * @throws Exception if no app-name was specified
987
	 */
988
	public function installApp($app,
989
							   \OCP\IConfig $config,
990
							   \OCP\IL10N $l) {
991
		if ($app !== false) {
992
			// check if the app is compatible with this version of ownCloud
993
			$info = self::getAppInfo($app);
994 View Code Duplication
			if(!is_array($info)) {
995
				throw new \Exception(
996
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
997
						[$info['name']]
998
					)
999
				);
1000
			}
1001
1002
			$version = \OCP\Util::getVersion();
1003 View Code Duplication
			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...
1004
				throw new \Exception(
1005
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1006
						array($info['name'])
1007
					)
1008
				);
1009
			}
1010
1011
			// check for required dependencies
1012
			self::checkAppDependencies($config, $l, $info);
1013
1014
			$config->setAppValue($app, 'enabled', 'yes');
1015
			if (isset($appData['id'])) {
0 ignored issues
show
Bug introduced by
The variable $appData seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1016
				$config->setAppValue($app, 'ocsid', $appData['id']);
1017
			}
1018
1019
			if(isset($info['settings']) && is_array($info['settings'])) {
1020
				$appPath = self::getAppPath($app);
1021
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1020 can also be of type false; however, OC_App::registerAutoloading() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1022
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1023
			}
1024
1025
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1026
		} else {
1027
			if(empty($appName) ) {
0 ignored issues
show
Bug introduced by
The variable $appName seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1028
				throw new \Exception($l->t("No app name specified"));
1029
			} else {
1030
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
1031
			}
1032
		}
1033
1034
		return $app;
1035
	}
1036
1037
	/**
1038
	 * update the database for the app and call the update script
1039
	 *
1040
	 * @param string $appId
1041
	 * @return bool
1042
	 */
1043
	public static function updateApp($appId) {
1044
		$appPath = self::getAppPath($appId);
1045
		if($appPath === false) {
1046
			return false;
1047
		}
1048
		self::registerAutoloading($appId, $appPath);
1049
1050
		$appData = self::getAppInfo($appId);
1051
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1052
1053
		if (file_exists($appPath . '/appinfo/database.xml')) {
1054
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1055
		} else {
1056
			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
1057
			$ms->migrate();
1058
		}
1059
1060
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1061
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1062
		unset(self::$appVersion[$appId]);
1063
1064
		// run upgrade code
1065
		if (file_exists($appPath . '/appinfo/update.php')) {
1066
			self::loadApp($appId);
1067
			include $appPath . '/appinfo/update.php';
1068
		}
1069
		self::setupBackgroundJobs($appData['background-jobs']);
1070
		if(isset($appData['settings']) && is_array($appData['settings'])) {
1071
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1072
		}
1073
1074
		//set remote/public handlers
1075
		if (array_key_exists('ocsid', $appData)) {
1076
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1077 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
1078
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1079
		}
1080
		foreach ($appData['remote'] as $name => $path) {
1081
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1082
		}
1083 View Code Duplication
		foreach ($appData['public'] as $name => $path) {
1084
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1085
		}
1086
1087
		self::setAppTypes($appId);
1088
1089
		$version = \OC_App::getAppVersion($appId);
1090
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
1091
1092
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1093
			ManagerEvent::EVENT_APP_UPDATE, $appId
1094
		));
1095
1096
		return true;
1097
	}
1098
1099
	/**
1100
	 * @param string $appId
1101
	 * @param string[] $steps
1102
	 * @throws \OC\NeedsUpdateException
1103
	 */
1104
	public static function executeRepairSteps($appId, array $steps) {
1105
		if (empty($steps)) {
1106
			return;
1107
		}
1108
		// load the app
1109
		self::loadApp($appId);
1110
1111
		$dispatcher = OC::$server->getEventDispatcher();
1112
1113
		// load the steps
1114
		$r = new Repair([], $dispatcher);
1115
		foreach ($steps as $step) {
1116
			try {
1117
				$r->addStep($step);
1118
			} catch (Exception $ex) {
1119
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1120
				\OC::$server->getLogger()->logException($ex);
1121
			}
1122
		}
1123
		// run the steps
1124
		$r->run();
1125
	}
1126
1127
	public static function setupBackgroundJobs(array $jobs) {
1128
		$queue = \OC::$server->getJobList();
1129
		foreach ($jobs as $job) {
1130
			$queue->add($job);
1131
		}
1132
	}
1133
1134
	/**
1135
	 * @param string $appId
1136
	 * @param string[] $steps
1137
	 */
1138
	private static function setupLiveMigrations($appId, array $steps) {
1139
		$queue = \OC::$server->getJobList();
1140
		foreach ($steps as $step) {
1141
			$queue->add('OC\Migration\BackgroundRepair', [
1142
				'app' => $appId,
1143
				'step' => $step]);
1144
		}
1145
	}
1146
1147
	/**
1148
	 * @param string $appId
1149
	 * @return \OC\Files\View|false
1150
	 */
1151
	public static function getStorage($appId) {
1152
		if (OC_App::isEnabled($appId)) { //sanity check
1153
			if (\OC::$server->getUserSession()->isLoggedIn()) {
1154
				$view = new \OC\Files\View('/' . OC_User::getUser());
1155
				if (!$view->file_exists($appId)) {
1156
					$view->mkdir($appId);
1157
				}
1158
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1159
			} else {
1160
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1161
				return false;
1162
			}
1163
		} else {
1164
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1165
			return false;
1166
		}
1167
	}
1168
1169
	protected static function findBestL10NOption($options, $lang) {
1170
		$fallback = $similarLangFallback = $englishFallback = false;
1171
1172
		$lang = strtolower($lang);
1173
		$similarLang = $lang;
1174
		if (strpos($similarLang, '_')) {
1175
			// For "de_DE" we want to find "de" and the other way around
1176
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1177
		}
1178
1179
		foreach ($options as $option) {
1180
			if (is_array($option)) {
1181
				if ($fallback === false) {
1182
					$fallback = $option['@value'];
1183
				}
1184
1185
				if (!isset($option['@attributes']['lang'])) {
1186
					continue;
1187
				}
1188
1189
				$attributeLang = strtolower($option['@attributes']['lang']);
1190
				if ($attributeLang === $lang) {
1191
					return $option['@value'];
1192
				}
1193
1194
				if ($attributeLang === $similarLang) {
1195
					$similarLangFallback = $option['@value'];
1196
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1197
					if ($similarLangFallback === false) {
1198
						$similarLangFallback =  $option['@value'];
1199
					}
1200
				}
1201
			} else {
1202
				$englishFallback = $option;
1203
			}
1204
		}
1205
1206
		if ($similarLangFallback !== false) {
1207
			return $similarLangFallback;
1208
		} else if ($englishFallback !== false) {
1209
			return $englishFallback;
1210
		}
1211
		return (string) $fallback;
1212
	}
1213
1214
	/**
1215
	 * parses the app data array and enhanced the 'description' value
1216
	 *
1217
	 * @param array $data the app data
1218
	 * @param string $lang
1219
	 * @return array improved app data
1220
	 */
1221
	public static function parseAppInfo(array $data, $lang = null) {
1222
1223 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...
1224
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1225
		}
1226 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...
1227
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1228
		}
1229
		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...
1230
			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1231
		} else if (isset($data['description']) && is_string($data['description'])) {
1232
			$data['description'] = trim($data['description']);
1233
		} else  {
1234
			$data['description'] = '';
1235
		}
1236
1237
		return $data;
1238
	}
1239
1240
	/**
1241
	 * @param \OCP\IConfig $config
1242
	 * @param \OCP\IL10N $l
1243
	 * @param array $info
1244
	 * @throws \Exception
1245
	 */
1246
	public static function checkAppDependencies($config, $l, $info) {
1247
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1248
		$missing = $dependencyAnalyzer->analyze($info);
1249
		if (!empty($missing)) {
1250
			$missingMsg = implode(PHP_EOL, $missing);
1251
			throw new \Exception(
1252
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1253
					[$info['name'], $missingMsg]
1254
				)
1255
			);
1256
		}
1257
	}
1258
}
1259