Completed
Push — master ( ca3aef...5b2d6b )
by Morris
15:49
created

OC_App::findAppInDirectories()   D

Complexity

Conditions 10
Paths 17

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 27
nc 17
nop 1
dl 0
loc 40
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
 * @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
	}
178
179
	/**
180
	 * @internal
181
	 * @param string $app
182
	 * @param string $path
183
	 */
184
	public static function registerAutoloading($app, $path) {
185
		$key = $app . '-' . $path;
186
		if(isset(self::$alreadyRegistered[$key])) {
187
			return;
188
		}
189
		self::$alreadyRegistered[$key] = true;
190
		// Register on PSR-4 composer autoloader
191
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
192
		\OC::$server->registerNamespace($app, $appNamespace);
193
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
194
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
195
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
196
		}
197
198
		// Register on legacy autoloader
199
		\OC::$loader->addValidRoot($path);
200
	}
201
202
	/**
203
	 * Load app.php from the given app
204
	 *
205
	 * @param string $app app name
206
	 */
207
	private static function requireAppFile($app) {
208
		try {
209
			// encapsulated here to avoid variable scope conflicts
210
			require_once $app . '/appinfo/app.php';
211
		} 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...
212
			\OC::$server->getLogger()->logException($ex);
213
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
214
			if (!in_array($app, $blacklist)) {
215
				self::disable($app);
216
			}
217
		}
218
	}
219
220
	/**
221
	 * check if an app is of a specific type
222
	 *
223
	 * @param string $app
224
	 * @param string|array $types
225
	 * @return bool
226
	 */
227
	public static function isType($app, $types) {
228
		if (is_string($types)) {
229
			$types = array($types);
230
		}
231
		$appTypes = self::getAppTypes($app);
232
		foreach ($types as $type) {
233
			if (array_search($type, $appTypes) !== false) {
234
				return true;
235
			}
236
		}
237
		return false;
238
	}
239
240
	/**
241
	 * get the types of an app
242
	 *
243
	 * @param string $app
244
	 * @return array
245
	 */
246
	private static function getAppTypes($app) {
247
		//load the cache
248
		if (count(self::$appTypes) == 0) {
249
			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...
250
		}
251
252
		if (isset(self::$appTypes[$app])) {
253
			return explode(',', self::$appTypes[$app]);
254
		} else {
255
			return array();
256
		}
257
	}
258
259
	/**
260
	 * read app types from info.xml and cache them in the database
261
	 */
262
	public static function setAppTypes($app) {
263
		$appData = self::getAppInfo($app);
264
		if(!is_array($appData)) {
265
			return;
266
		}
267
268
		if (isset($appData['types'])) {
269
			$appTypes = implode(',', $appData['types']);
270
		} else {
271
			$appTypes = '';
272
			$appData['types'] = [];
273
		}
274
275
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
276
277
		if (\OC::$server->getAppManager()->hasProtectedAppType($appData['types'])) {
278
			$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'yes');
279
			if ($enabled !== 'yes' && $enabled !== 'no') {
280
				\OC::$server->getAppConfig()->setValue($app, 'enabled', 'yes');
281
			}
282
		}
283
	}
284
285
	/**
286
	 * get all enabled apps
287
	 */
288
	protected static $enabledAppsCache = array();
289
290
	/**
291
	 * Returns apps enabled for the current user.
292
	 *
293
	 * @param bool $forceRefresh whether to refresh the cache
294
	 * @param bool $all whether to return apps for all users, not only the
295
	 * currently logged in one
296
	 * @return string[]
297
	 */
298
	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...
299
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
300
			return array();
301
		}
302
		// in incognito mode or when logged out, $user will be false,
303
		// which is also the case during an upgrade
304
		$appManager = \OC::$server->getAppManager();
305
		if ($all) {
306
			$user = null;
307
		} else {
308
			$user = \OC::$server->getUserSession()->getUser();
309
		}
310
311
		if (is_null($user)) {
312
			$apps = $appManager->getInstalledApps();
313
		} else {
314
			$apps = $appManager->getEnabledAppsForUser($user);
315
		}
316
		$apps = array_filter($apps, function ($app) {
317
			return $app !== 'files';//we add this manually
318
		});
319
		sort($apps);
320
		array_unshift($apps, 'files');
321
		return $apps;
322
	}
323
324
	/**
325
	 * checks whether or not an app is enabled
326
	 *
327
	 * @param string $app app
328
	 * @return bool
329
	 *
330
	 * This function checks whether or not an app is enabled.
331
	 */
332
	public static function isEnabled($app) {
333
		return \OC::$server->getAppManager()->isEnabledForUser($app);
334
	}
335
336
	/**
337
	 * enables an app
338
	 *
339
	 * @param string $appId
340
	 * @param array $groups (optional) when set, only these groups will have access to the app
341
	 * @throws \Exception
342
	 * @return void
343
	 *
344
	 * This function set an app as enabled in appconfig.
345
	 */
346
	public function enable($appId,
347
						   $groups = null) {
348
		self::$enabledAppsCache = []; // flush
349
350
		// Check if app is already downloaded
351
		$installer = new Installer(
352
			\OC::$server->getAppFetcher(),
353
			\OC::$server->getHTTPClientService(),
354
			\OC::$server->getTempManager(),
355
			\OC::$server->getLogger(),
356
			\OC::$server->getConfig()
357
		);
358
		$isDownloaded = $installer->isDownloaded($appId);
359
360
		if(!$isDownloaded) {
361
			$installer->downloadApp($appId);
362
		}
363
364
		$installer->installApp($appId);
365
366
		$appManager = \OC::$server->getAppManager();
367
		if (!is_null($groups)) {
368
			$groupManager = \OC::$server->getGroupManager();
369
			$groupsList = [];
370
			foreach ($groups as $group) {
371
				$groupItem = $groupManager->get($group);
372
				if ($groupItem instanceof \OCP\IGroup) {
373
					$groupsList[] = $groupManager->get($group);
374
				}
375
			}
376
			$appManager->enableAppForGroups($appId, $groupsList);
377
		} else {
378
			$appManager->enableApp($appId);
379
		}
380
	}
381
382
	/**
383
	 * @param string $app
384
	 * @return bool
385
	 */
386
	public static function removeApp($app) {
387
		if (\OC::$server->getAppManager()->isShipped($app)) {
388
			return false;
389
		}
390
391
		$installer = new Installer(
392
			\OC::$server->getAppFetcher(),
393
			\OC::$server->getHTTPClientService(),
394
			\OC::$server->getTempManager(),
395
			\OC::$server->getLogger(),
396
			\OC::$server->getConfig()
397
		);
398
		return $installer->removeApp($app);
399
	}
400
401
	/**
402
	 * This function set an app as disabled in appconfig.
403
	 *
404
	 * @param string $app app
405
	 * @throws Exception
406
	 */
407
	public static function disable($app) {
408
		// flush
409
		self::$enabledAppsCache = array();
410
411
		// run uninstall steps
412
		$appData = OC_App::getAppInfo($app);
413
		if (!is_null($appData)) {
414
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
415
		}
416
417
		// emit disable hook - needed anymore ?
418
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
419
420
		// finally disable it
421
		$appManager = \OC::$server->getAppManager();
422
		$appManager->disableApp($app);
423
	}
424
425
	// This is private as well. It simply works, so don't ask for more details
426
	private static function proceedNavigation($list) {
427
		usort($list, function($a, $b) {
428
			if (isset($a['order']) && isset($b['order'])) {
429
				return ($a['order'] < $b['order']) ? -1 : 1;
430
			} else if (isset($a['order']) || isset($b['order'])) {
431
				return isset($a['order']) ? -1 : 1;
432
			} else {
433
				return ($a['name'] < $b['name']) ? -1 : 1;
434
			}
435
		});
436
437
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
438
		foreach ($list as $index => &$navEntry) {
439
			if ($navEntry['id'] == $activeApp) {
440
				$navEntry['active'] = true;
441
			} else {
442
				$navEntry['active'] = false;
443
			}
444
		}
445
		unset($navEntry);
446
447
		return $list;
448
	}
449
450
	/**
451
	 * Get the path where to install apps
452
	 *
453
	 * @return string|false
454
	 */
455
	public static function getInstallPath() {
456
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
457
			return false;
458
		}
459
460
		foreach (OC::$APPSROOTS as $dir) {
461
			if (isset($dir['writable']) && $dir['writable'] === true) {
462
				return $dir['path'];
463
			}
464
		}
465
466
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
467
		return null;
468
	}
469
470
471
	/**
472
	 * search for an app in all app-directories
473
	 *
474
	 * @param string $appId
475
	 * @return false|string
476
	 */
477
	public static function findAppInDirectories($appId) {
478
		$sanitizedAppId = self::cleanAppId($appId);
479
		if($sanitizedAppId !== $appId) {
480
			return false;
481
		}
482
		static $app_dir = array();
483
484
		if (isset($app_dir[$appId])) {
485
			return $app_dir[$appId];
486
		}
487
488
		$possibleApps = array();
489
		foreach (OC::$APPSROOTS as $dir) {
490
			if (file_exists($dir['path'] . '/' . $appId)) {
491
				$possibleApps[] = $dir;
492
			}
493
		}
494
495
		if (empty($possibleApps)) {
496
			return false;
497
		} elseif (count($possibleApps) === 1) {
498
			$dir = array_shift($possibleApps);
499
			$app_dir[$appId] = $dir;
500
			return $dir;
501
		} else {
502
			$versionToLoad = array();
503
			foreach ($possibleApps as $possibleApp) {
504
				$version = self::getAppVersionByPath($possibleApp['path']);
505
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
506
					$versionToLoad = array(
507
						'dir' => $possibleApp,
508
						'version' => $version,
509
					);
510
				}
511
			}
512
			$app_dir[$appId] = $versionToLoad['dir'];
513
			return $versionToLoad['dir'];
514
			//TODO - write test
515
		}
516
	}
517
518
	/**
519
	 * Get the directory for the given app.
520
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
521
	 *
522
	 * @param string $appId
523
	 * @return string|false
524
	 */
525
	public static function getAppPath($appId) {
526
		if ($appId === null || trim($appId) === '') {
527
			return false;
528
		}
529
530
		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...
531
			return $dir['path'] . '/' . $appId;
532
		}
533
		return false;
534
	}
535
536
	/**
537
	 * Get the path for the given app on the access
538
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
539
	 *
540
	 * @param string $appId
541
	 * @return string|false
542
	 */
543
	public static function getAppWebPath($appId) {
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 OC::$WEBROOT . $dir['url'] . '/' . $appId;
546
		}
547
		return false;
548
	}
549
550
	/**
551
	 * get the last version of the app from appinfo/info.xml
552
	 *
553
	 * @param string $appId
554
	 * @param bool $useCache
555
	 * @return string
556
	 */
557
	public static function getAppVersion($appId, $useCache = true) {
558
		if($useCache && isset(self::$appVersion[$appId])) {
559
			return self::$appVersion[$appId];
560
		}
561
562
		$file = self::getAppPath($appId);
563
		self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
564
		return self::$appVersion[$appId];
565
	}
566
567
	/**
568
	 * get app's version based on it's path
569
	 *
570
	 * @param string $path
571
	 * @return string
572
	 */
573
	public static function getAppVersionByPath($path) {
574
		$infoFile = $path . '/appinfo/info.xml';
575
		$appData = self::getAppInfo($infoFile, true);
576
		return isset($appData['version']) ? $appData['version'] : '';
577
	}
578
579
580
	/**
581
	 * Read all app metadata from the info.xml file
582
	 *
583
	 * @param string $appId id of the app or the path of the info.xml file
584
	 * @param bool $path
585
	 * @param string $lang
586
	 * @return array|null
587
	 * @note all data is read from info.xml, not just pre-defined fields
588
	 */
589
	public static function getAppInfo($appId, $path = false, $lang = null) {
590
		if ($path) {
591
			$file = $appId;
592
		} else {
593
			if ($lang === null && isset(self::$appInfo[$appId])) {
594
				return self::$appInfo[$appId];
595
			}
596
			$appPath = self::getAppPath($appId);
597
			if($appPath === false) {
598
				return null;
599
			}
600
			$file = $appPath . '/appinfo/info.xml';
601
		}
602
603
		$parser = new InfoParser(\OC::$server->getMemCacheFactory()->create('core.appinfo'));
604
		$data = $parser->parse($file);
605
606
		if (is_array($data)) {
607
			$data = OC_App::parseAppInfo($data, $lang);
608
		}
609
		if(isset($data['ocsid'])) {
610
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
611
			if($storedId !== '' && $storedId !== $data['ocsid']) {
612
				$data['ocsid'] = $storedId;
613
			}
614
		}
615
616
		if ($lang === null) {
617
			self::$appInfo[$appId] = $data;
618
		}
619
620
		return $data;
621
	}
622
623
	/**
624
	 * Returns the navigation
625
	 *
626
	 * @return array
627
	 *
628
	 * This function returns an array containing all entries added. The
629
	 * entries are sorted by the key 'order' ascending. Additional to the keys
630
	 * given for each app the following keys exist:
631
	 *   - active: boolean, signals if the user is on this navigation entry
632
	 */
633
	public static function getNavigation() {
634
		$entries = OC::$server->getNavigationManager()->getAll();
635
		return self::proceedNavigation($entries);
636
	}
637
638
	/**
639
	 * Returns the Settings Navigation
640
	 *
641
	 * @return string[]
642
	 *
643
	 * This function returns an array containing all settings pages added. The
644
	 * entries are sorted by the key 'order' ascending.
645
	 */
646
	public static function getSettingsNavigation() {
647
		$entries = OC::$server->getNavigationManager()->getAll('settings');
648
		return self::proceedNavigation($entries);
649
	}
650
651
	/**
652
	 * get the id of loaded app
653
	 *
654
	 * @return string
655
	 */
656
	public static function getCurrentApp() {
657
		$request = \OC::$server->getRequest();
658
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
659
		$topFolder = substr($script, 0, strpos($script, '/'));
660
		if (empty($topFolder)) {
661
			$path_info = $request->getPathInfo();
662
			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...
663
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
664
			}
665
		}
666
		if ($topFolder == 'apps') {
667
			$length = strlen($topFolder);
668
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
669
		} else {
670
			return $topFolder;
671
		}
672
	}
673
674
	/**
675
	 * @param string $type
676
	 * @return array
677
	 */
678
	public static function getForms($type) {
679
		$forms = array();
680
		switch ($type) {
681
			case 'admin':
682
				$source = self::$adminForms;
683
				break;
684
			case 'personal':
685
				$source = self::$personalForms;
686
				break;
687
			default:
688
				return array();
689
		}
690
		foreach ($source as $form) {
691
			$forms[] = include $form;
692
		}
693
		return $forms;
694
	}
695
696
	/**
697
	 * register an admin form to be shown
698
	 *
699
	 * @param string $app
700
	 * @param string $page
701
	 */
702
	public static function registerAdmin($app, $page) {
703
		self::$adminForms[] = $app . '/' . $page . '.php';
704
	}
705
706
	/**
707
	 * register a personal form to be shown
708
	 * @param string $app
709
	 * @param string $page
710
	 */
711
	public static function registerPersonal($app, $page) {
712
		self::$personalForms[] = $app . '/' . $page . '.php';
713
	}
714
715
	/**
716
	 * @param array $entry
717
	 */
718
	public static function registerLogIn(array $entry) {
719
		self::$altLogin[] = $entry;
720
	}
721
722
	/**
723
	 * @return array
724
	 */
725
	public static function getAlternativeLogIns() {
726
		return self::$altLogin;
727
	}
728
729
	/**
730
	 * get a list of all apps in the apps folder
731
	 *
732
	 * @return array an array of app names (string IDs)
733
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
734
	 */
735
	public static function getAllApps() {
736
737
		$apps = array();
738
739
		foreach (OC::$APPSROOTS as $apps_dir) {
740
			if (!is_readable($apps_dir['path'])) {
741
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
742
				continue;
743
			}
744
			$dh = opendir($apps_dir['path']);
745
746
			if (is_resource($dh)) {
747
				while (($file = readdir($dh)) !== false) {
748
749
					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...
750
751
						$apps[] = $file;
752
					}
753
				}
754
			}
755
		}
756
757
		return $apps;
758
	}
759
760
	/**
761
	 * List all apps, this is used in apps.php
762
	 *
763
	 * @return array
764
	 */
765
	public function listAllApps() {
766
		$installedApps = OC_App::getAllApps();
767
768
		$appManager = \OC::$server->getAppManager();
769
		//we don't want to show configuration for these
770
		$blacklist = $appManager->getAlwaysEnabledApps();
771
		$appList = array();
772
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
773
		$urlGenerator = \OC::$server->getURLGenerator();
774
775
		foreach ($installedApps as $app) {
776
			if (array_search($app, $blacklist) === false) {
777
778
				$info = OC_App::getAppInfo($app, false, $langCode);
779
				if (!is_array($info)) {
780
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
781
					continue;
782
				}
783
784
				if (!isset($info['name'])) {
785
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
786
					continue;
787
				}
788
789
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
790
				$info['groups'] = null;
791
				if ($enabled === 'yes') {
792
					$active = true;
793
				} else if ($enabled === 'no') {
794
					$active = false;
795
				} else {
796
					$active = true;
797
					$info['groups'] = $enabled;
798
				}
799
800
				$info['active'] = $active;
801
802
				if ($appManager->isShipped($app)) {
803
					$info['internal'] = true;
804
					$info['level'] = self::officialApp;
805
					$info['removable'] = false;
806
				} else {
807
					$info['internal'] = false;
808
					$info['removable'] = true;
809
				}
810
811
				$appPath = self::getAppPath($app);
812
				if($appPath !== false) {
813
					$appIcon = $appPath . '/img/' . $app . '.svg';
814
					if (file_exists($appIcon)) {
815
						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
816
						$info['previewAsIcon'] = true;
817
					} else {
818
						$appIcon = $appPath . '/img/app.svg';
819
						if (file_exists($appIcon)) {
820
							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
821
							$info['previewAsIcon'] = true;
822
						}
823
					}
824
				}
825
				// fix documentation
826
				if (isset($info['documentation']) && is_array($info['documentation'])) {
827
					foreach ($info['documentation'] as $key => $url) {
828
						// If it is not an absolute URL we assume it is a key
829
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
830
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
831
							$url = $urlGenerator->linkToDocs($url);
832
						}
833
834
						$info['documentation'][$key] = $url;
835
					}
836
				}
837
838
				$info['version'] = OC_App::getAppVersion($app);
839
				$appList[] = $info;
840
			}
841
		}
842
843
		return $appList;
844
	}
845
846
	/**
847
	 * Returns the internal app ID or false
848
	 * @param string $ocsID
849
	 * @return string|false
850
	 */
851
	public static function getInternalAppIdByOcs($ocsID) {
852
		if(is_numeric($ocsID)) {
853
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
854
			if(array_search($ocsID, $idArray)) {
855
				return array_search($ocsID, $idArray);
856
			}
857
		}
858
		return false;
859
	}
860
861
	public static function shouldUpgrade($app) {
862
		$versions = self::getAppVersions();
863
		$currentVersion = OC_App::getAppVersion($app);
864
		if ($currentVersion && isset($versions[$app])) {
865
			$installedVersion = $versions[$app];
866
			if (!version_compare($currentVersion, $installedVersion, '=')) {
867
				return true;
868
			}
869
		}
870
		return false;
871
	}
872
873
	/**
874
	 * Adjust the number of version parts of $version1 to match
875
	 * the number of version parts of $version2.
876
	 *
877
	 * @param string $version1 version to adjust
878
	 * @param string $version2 version to take the number of parts from
879
	 * @return string shortened $version1
880
	 */
881
	private static function adjustVersionParts($version1, $version2) {
882
		$version1 = explode('.', $version1);
883
		$version2 = explode('.', $version2);
884
		// reduce $version1 to match the number of parts in $version2
885
		while (count($version1) > count($version2)) {
886
			array_pop($version1);
887
		}
888
		// if $version1 does not have enough parts, add some
889
		while (count($version1) < count($version2)) {
890
			$version1[] = '0';
891
		}
892
		return implode('.', $version1);
893
	}
894
895
	/**
896
	 * Check whether the current ownCloud version matches the given
897
	 * application's version requirements.
898
	 *
899
	 * The comparison is made based on the number of parts that the
900
	 * app info version has. For example for ownCloud 6.0.3 if the
901
	 * app info version is expecting version 6.0, the comparison is
902
	 * made on the first two parts of the ownCloud version.
903
	 * This means that it's possible to specify "requiremin" => 6
904
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
905
	 *
906
	 * @param string $ocVersion ownCloud version to check against
907
	 * @param array $appInfo app info (from xml)
908
	 *
909
	 * @return boolean true if compatible, otherwise false
910
	 */
911
	public static function isAppCompatible($ocVersion, $appInfo) {
912
		$requireMin = '';
913
		$requireMax = '';
914
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
915
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
916 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
917
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
918
		} else if (isset($appInfo['requiremin'])) {
919
			$requireMin = $appInfo['requiremin'];
920
		} else if (isset($appInfo['require'])) {
921
			$requireMin = $appInfo['require'];
922
		}
923
924
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
925
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
926 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
927
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
928
		} else if (isset($appInfo['requiremax'])) {
929
			$requireMax = $appInfo['requiremax'];
930
		}
931
932
		if (is_array($ocVersion)) {
933
			$ocVersion = implode('.', $ocVersion);
934
		}
935
936 View Code Duplication
		if (!empty($requireMin)
937
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
938
		) {
939
940
			return false;
941
		}
942
943 View Code Duplication
		if (!empty($requireMax)
944
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
945
		) {
946
			return false;
947
		}
948
949
		return true;
950
	}
951
952
	/**
953
	 * get the installed version of all apps
954
	 */
955
	public static function getAppVersions() {
956
		static $versions;
957
958
		if(!$versions) {
959
			$appConfig = \OC::$server->getAppConfig();
960
			$versions = $appConfig->getValues(false, 'installed_version');
961
		}
962
		return $versions;
963
	}
964
965
	/**
966
	 * @param string $app
967
	 * @param \OCP\IConfig $config
968
	 * @param \OCP\IL10N $l
969
	 * @return bool
970
	 *
971
	 * @throws Exception if app is not compatible with this version of ownCloud
972
	 * @throws Exception if no app-name was specified
973
	 */
974
	public function installApp($app,
975
							   \OCP\IConfig $config,
976
							   \OCP\IL10N $l) {
977
		if ($app !== false) {
978
			// check if the app is compatible with this version of ownCloud
979
			$info = self::getAppInfo($app);
980 View Code Duplication
			if(!is_array($info)) {
981
				throw new \Exception(
982
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
983
						[$info['name']]
984
					)
985
				);
986
			}
987
988
			$version = \OCP\Util::getVersion();
989 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...
990
				throw new \Exception(
991
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
992
						array($info['name'])
993
					)
994
				);
995
			}
996
997
			// check for required dependencies
998
			self::checkAppDependencies($config, $l, $info);
999
1000
			$config->setAppValue($app, 'enabled', 'yes');
1001
			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...
1002
				$config->setAppValue($app, 'ocsid', $appData['id']);
1003
			}
1004
1005
			if(isset($info['settings']) && is_array($info['settings'])) {
1006
				$appPath = self::getAppPath($app);
1007
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1006 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...
1008
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1009
			}
1010
1011
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1012
		} else {
1013
			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...
1014
				throw new \Exception($l->t("No app name specified"));
1015
			} else {
1016
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
1017
			}
1018
		}
1019
1020
		return $app;
1021
	}
1022
1023
	/**
1024
	 * update the database for the app and call the update script
1025
	 *
1026
	 * @param string $appId
1027
	 * @return bool
1028
	 */
1029
	public static function updateApp($appId) {
1030
		$appPath = self::getAppPath($appId);
1031
		if($appPath === false) {
1032
			return false;
1033
		}
1034
		self::registerAutoloading($appId, $appPath);
1035
1036
		$appData = self::getAppInfo($appId);
1037
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1038
1039
		if (file_exists($appPath . '/appinfo/database.xml')) {
1040
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1041
		} else {
1042
			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
1043
			$ms->migrate();
1044
		}
1045
1046
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1047
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1048
		unset(self::$appVersion[$appId]);
1049
1050
		// run upgrade code
1051
		if (file_exists($appPath . '/appinfo/update.php')) {
1052
			self::loadApp($appId);
1053
			include $appPath . '/appinfo/update.php';
1054
		}
1055
		self::setupBackgroundJobs($appData['background-jobs']);
1056
		if(isset($appData['settings']) && is_array($appData['settings'])) {
1057
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1058
		}
1059
1060
		//set remote/public handlers
1061
		if (array_key_exists('ocsid', $appData)) {
1062
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1063 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
1064
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1065
		}
1066
		foreach ($appData['remote'] as $name => $path) {
1067
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1068
		}
1069 View Code Duplication
		foreach ($appData['public'] as $name => $path) {
1070
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1071
		}
1072
1073
		self::setAppTypes($appId);
1074
1075
		$version = \OC_App::getAppVersion($appId);
1076
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
1077
1078
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1079
			ManagerEvent::EVENT_APP_UPDATE, $appId
1080
		));
1081
1082
		return true;
1083
	}
1084
1085
	/**
1086
	 * @param string $appId
1087
	 * @param string[] $steps
1088
	 * @throws \OC\NeedsUpdateException
1089
	 */
1090
	public static function executeRepairSteps($appId, array $steps) {
1091
		if (empty($steps)) {
1092
			return;
1093
		}
1094
		// load the app
1095
		self::loadApp($appId);
1096
1097
		$dispatcher = OC::$server->getEventDispatcher();
1098
1099
		// load the steps
1100
		$r = new Repair([], $dispatcher);
1101
		foreach ($steps as $step) {
1102
			try {
1103
				$r->addStep($step);
1104
			} catch (Exception $ex) {
1105
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1106
				\OC::$server->getLogger()->logException($ex);
1107
			}
1108
		}
1109
		// run the steps
1110
		$r->run();
1111
	}
1112
1113
	public static function setupBackgroundJobs(array $jobs) {
1114
		$queue = \OC::$server->getJobList();
1115
		foreach ($jobs as $job) {
1116
			$queue->add($job);
1117
		}
1118
	}
1119
1120
	/**
1121
	 * @param string $appId
1122
	 * @param string[] $steps
1123
	 */
1124
	private static function setupLiveMigrations($appId, array $steps) {
1125
		$queue = \OC::$server->getJobList();
1126
		foreach ($steps as $step) {
1127
			$queue->add('OC\Migration\BackgroundRepair', [
1128
				'app' => $appId,
1129
				'step' => $step]);
1130
		}
1131
	}
1132
1133
	/**
1134
	 * @param string $appId
1135
	 * @return \OC\Files\View|false
1136
	 */
1137
	public static function getStorage($appId) {
1138
		if (OC_App::isEnabled($appId)) { //sanity check
1139
			if (\OC::$server->getUserSession()->isLoggedIn()) {
1140
				$view = new \OC\Files\View('/' . OC_User::getUser());
1141
				if (!$view->file_exists($appId)) {
1142
					$view->mkdir($appId);
1143
				}
1144
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1145
			} else {
1146
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1147
				return false;
1148
			}
1149
		} else {
1150
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1151
			return false;
1152
		}
1153
	}
1154
1155
	protected static function findBestL10NOption($options, $lang) {
1156
		$fallback = $similarLangFallback = $englishFallback = false;
1157
1158
		$lang = strtolower($lang);
1159
		$similarLang = $lang;
1160
		if (strpos($similarLang, '_')) {
1161
			// For "de_DE" we want to find "de" and the other way around
1162
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1163
		}
1164
1165
		foreach ($options as $option) {
1166
			if (is_array($option)) {
1167
				if ($fallback === false) {
1168
					$fallback = $option['@value'];
1169
				}
1170
1171
				if (!isset($option['@attributes']['lang'])) {
1172
					continue;
1173
				}
1174
1175
				$attributeLang = strtolower($option['@attributes']['lang']);
1176
				if ($attributeLang === $lang) {
1177
					return $option['@value'];
1178
				}
1179
1180
				if ($attributeLang === $similarLang) {
1181
					$similarLangFallback = $option['@value'];
1182
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1183
					if ($similarLangFallback === false) {
1184
						$similarLangFallback =  $option['@value'];
1185
					}
1186
				}
1187
			} else {
1188
				$englishFallback = $option;
1189
			}
1190
		}
1191
1192
		if ($similarLangFallback !== false) {
1193
			return $similarLangFallback;
1194
		} else if ($englishFallback !== false) {
1195
			return $englishFallback;
1196
		}
1197
		return (string) $fallback;
1198
	}
1199
1200
	/**
1201
	 * parses the app data array and enhanced the 'description' value
1202
	 *
1203
	 * @param array $data the app data
1204
	 * @param string $lang
1205
	 * @return array improved app data
1206
	 */
1207
	public static function parseAppInfo(array $data, $lang = null) {
1208
1209 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...
1210
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1211
		}
1212 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...
1213
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1214
		}
1215
		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...
1216
			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1217
		} else if (isset($data['description']) && is_string($data['description'])) {
1218
			$data['description'] = trim($data['description']);
1219
		} else  {
1220
			$data['description'] = '';
1221
		}
1222
1223
		return $data;
1224
	}
1225
1226
	/**
1227
	 * @param \OCP\IConfig $config
1228
	 * @param \OCP\IL10N $l
1229
	 * @param array $info
1230
	 * @throws \Exception
1231
	 */
1232
	public static function checkAppDependencies($config, $l, $info) {
1233
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1234
		$missing = $dependencyAnalyzer->analyze($info);
1235
		if (!empty($missing)) {
1236
			$missingMsg = implode(PHP_EOL, $missing);
1237
			throw new \Exception(
1238
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1239
					[$info['name'], $missingMsg]
1240
				)
1241
			);
1242
		}
1243
	}
1244
}
1245