Completed
Push — master ( a82b56...5d84da )
by Morris
23:05 queued 10:09
created

OC_App::adjustVersionParts()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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