Completed
Push — master ( 815616...da0d0d )
by Lukas
11:26
created

OC_App::setupLiveMigrations()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Bernhard Posselt <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Borjan Tchakaloff <[email protected]>
10
 * @author Brice Maron <[email protected]>
11
 * @author Christopher Schäpers <[email protected]>
12
 * @author Felix Moeller <[email protected]>
13
 * @author Frank Karlitschek <[email protected]>
14
 * @author Georg Ehrke <[email protected]>
15
 * @author Jakob Sack <[email protected]>
16
 * @author Jan-Christoph Borchardt <[email protected]>
17
 * @author Joas Schilling <[email protected]>
18
 * @author Jörn Friedrich Dreyer <[email protected]>
19
 * @author Kamil Domanski <[email protected]>
20
 * @author Klaas Freitag <[email protected]>
21
 * @author Lukas Reschke <[email protected]>
22
 * @author Markus Goetz <[email protected]>
23
 * @author Morris Jobke <[email protected]>
24
 * @author RealRancor <[email protected]>
25
 * @author Robin Appelman <[email protected]>
26
 * @author Robin McCorkell <[email protected]>
27
 * @author Roeland Jago Douma <[email protected]>
28
 * @author Sam Tuke <[email protected]>
29
 * @author Thomas Müller <[email protected]>
30
 * @author Thomas Tanghus <[email protected]>
31
 * @author Tom Needham <[email protected]>
32
 * @author Vincent Petry <[email protected]>
33
 *
34
 * @license AGPL-3.0
35
 *
36
 * This code is free software: you can redistribute it and/or modify
37
 * it under the terms of the GNU Affero General Public License, version 3,
38
 * as published by the Free Software Foundation.
39
 *
40
 * This program is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43
 * GNU Affero General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Affero General Public License, version 3,
46
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
47
 *
48
 */
49
use OC\App\DependencyAnalyzer;
50
use OC\App\Platform;
51
use OC\Installer;
52
use OC\OCSClient;
53
use OC\Repair;
54
use OCP\App\ManagerEvent;
55
56
/**
57
 * This class manages the apps. It allows them to register and integrate in the
58
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
59
 * upgrading and removing apps.
60
 */
61
class OC_App {
62
	static private $appVersion = [];
63
	static private $adminForms = array();
64
	static private $personalForms = array();
65
	static private $appInfo = array();
66
	static private $appTypes = array();
67
	static private $loadedApps = array();
68
	static private $altLogin = array();
69
	static private $alreadyRegistered = [];
70
	const officialApp = 200;
71
72
	/**
73
	 * clean the appId
74
	 *
75
	 * @param string|boolean $app AppId that needs to be cleaned
76
	 * @return string
77
	 */
78
	public static function cleanAppId($app) {
79
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
80
	}
81
82
	/**
83
	 * Check if an app is loaded
84
	 *
85
	 * @param string $app
86
	 * @return bool
87
	 */
88
	public static function isAppLoaded($app) {
89
		return in_array($app, self::$loadedApps, true);
90
	}
91
92
	/**
93
	 * loads all apps
94
	 *
95
	 * @param string[] | string | null $types
96
	 * @return bool
97
	 *
98
	 * This function walks through the ownCloud directory and loads all apps
99
	 * it can find. A directory contains an app if the file /appinfo/info.xml
100
	 * exists.
101
	 *
102
	 * if $types is set, only apps of those types will be loaded
103
	 */
104
	public static function loadApps($types = null) {
105
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
106
			return false;
107
		}
108
		// Load the enabled apps here
109
		$apps = self::getEnabledApps();
110
111
		// Add each apps' folder as allowed class path
112
		foreach($apps as $app) {
113
			$path = self::getAppPath($app);
114
			if($path !== false) {
115
				self::registerAutoloading($app, $path);
116
			}
117
		}
118
119
		// prevent app.php from printing output
120
		ob_start();
121
		foreach ($apps as $app) {
122
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
123
				self::loadApp($app);
124
			}
125
		}
126
		ob_end_clean();
127
128
		return true;
129
	}
130
131
	/**
132
	 * load a single app
133
	 *
134
	 * @param string $app
135
	 * @param bool $checkUpgrade whether an upgrade check should be done
136
	 * @throws \OC\NeedsUpdateException
137
	 */
138
	public static function loadApp($app, $checkUpgrade = true) {
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
			if ($checkUpgrade and self::shouldUpgrade($app)) {
151
				throw new \OC\NeedsUpdateException();
152
			}
153
			self::requireAppFile($app);
154
			if (self::isType($app, array('authentication'))) {
155
				// since authentication apps affect the "is app enabled for group" check,
156
				// the enabled apps cache needs to be cleared to make sure that the
157
				// next time getEnableApps() is called it will also include apps that were
158
				// enabled for groups
159
				self::$enabledAppsCache = array();
160
			}
161
			\OC::$server->getEventLogger()->end('load_app_' . $app);
162
		}
163
	}
164
165
	/**
166
	 * @internal
167
	 * @param string $app
168
	 * @param string $path
169
	 */
170
	public static function registerAutoloading($app, $path) {
171
		$key = $app . '-' . $path;
172
		if(isset(self::$alreadyRegistered[$key])) {
173
			return;
174
		}
175
		self::$alreadyRegistered[$key] = true;
176
		// Register on PSR-4 composer autoloader
177
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
178
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
179
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
180
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
181
		}
182
183
		// Register on legacy autoloader
184
		\OC::$loader->addValidRoot($path);
185
	}
186
187
	/**
188
	 * Load app.php from the given app
189
	 *
190
	 * @param string $app app name
191
	 */
192
	private static function requireAppFile($app) {
193
		try {
194
			// encapsulated here to avoid variable scope conflicts
195
			require_once $app . '/appinfo/app.php';
196
		} 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...
197
			\OC::$server->getLogger()->logException($ex);
198
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
199
			if (!in_array($app, $blacklist)) {
200
				self::disable($app);
201
			}
202
		}
203
	}
204
205
	/**
206
	 * check if an app is of a specific type
207
	 *
208
	 * @param string $app
209
	 * @param string|array $types
210
	 * @return bool
211
	 */
212
	public static function isType($app, $types) {
213
		if (is_string($types)) {
214
			$types = array($types);
215
		}
216
		$appTypes = self::getAppTypes($app);
217
		foreach ($types as $type) {
218
			if (array_search($type, $appTypes) !== false) {
219
				return true;
220
			}
221
		}
222
		return false;
223
	}
224
225
	/**
226
	 * get the types of an app
227
	 *
228
	 * @param string $app
229
	 * @return array
230
	 */
231
	private static function getAppTypes($app) {
232
		//load the cache
233
		if (count(self::$appTypes) == 0) {
234
			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...
235
		}
236
237
		if (isset(self::$appTypes[$app])) {
238
			return explode(',', self::$appTypes[$app]);
239
		} else {
240
			return array();
241
		}
242
	}
243
244
	/**
245
	 * read app types from info.xml and cache them in the database
246
	 */
247
	public static function setAppTypes($app) {
248
		$appData = self::getAppInfo($app);
249
		if(!is_array($appData)) {
250
			return;
251
		}
252
253
		if (isset($appData['types'])) {
254
			$appTypes = implode(',', $appData['types']);
255
		} else {
256
			$appTypes = '';
257
		}
258
259
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
260
	}
261
262
	/**
263
	 * check if app is shipped
264
	 *
265
	 * @param string $appId the id of the app to check
266
	 * @return bool
267
	 *
268
	 * Check if an app that is installed is a shipped app or installed from the appstore.
269
	 */
270
	public static function isShipped($appId) {
271
		return \OC::$server->getAppManager()->isShipped($appId);
272
	}
273
274
	/**
275
	 * get all enabled apps
276
	 */
277
	protected static $enabledAppsCache = array();
278
279
	/**
280
	 * Returns apps enabled for the current user.
281
	 *
282
	 * @param bool $forceRefresh whether to refresh the cache
283
	 * @param bool $all whether to return apps for all users, not only the
284
	 * currently logged in one
285
	 * @return string[]
286
	 */
287
	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...
288
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
289
			return array();
290
		}
291
		// in incognito mode or when logged out, $user will be false,
292
		// which is also the case during an upgrade
293
		$appManager = \OC::$server->getAppManager();
294
		if ($all) {
295
			$user = null;
296
		} else {
297
			$user = \OC::$server->getUserSession()->getUser();
298
		}
299
300
		if (is_null($user)) {
301
			$apps = $appManager->getInstalledApps();
302
		} else {
303
			$apps = $appManager->getEnabledAppsForUser($user);
304
		}
305
		$apps = array_filter($apps, function ($app) {
306
			return $app !== 'files';//we add this manually
307
		});
308
		sort($apps);
309
		array_unshift($apps, 'files');
310
		return $apps;
311
	}
312
313
	/**
314
	 * checks whether or not an app is enabled
315
	 *
316
	 * @param string $app app
317
	 * @return bool
318
	 *
319
	 * This function checks whether or not an app is enabled.
320
	 */
321
	public static function isEnabled($app) {
322
		return \OC::$server->getAppManager()->isEnabledForUser($app);
323
	}
324
325
	/**
326
	 * enables an app
327
	 *
328
	 * @param mixed $app app
329
	 * @param array $groups (optional) when set, only these groups will have access to the app
330
	 * @throws \Exception
331
	 * @return void
332
	 *
333
	 * This function set an app as enabled in appconfig.
334
	 */
335
	public static function enable($app, $groups = null) {
336
		self::$enabledAppsCache = array(); // flush
337
		if (!Installer::isInstalled($app)) {
338
			$app = self::installApp($app);
339
		}
340
341
		$appManager = \OC::$server->getAppManager();
342
		if (!is_null($groups)) {
343
			$groupManager = \OC::$server->getGroupManager();
344
			$groupsList = [];
345
			foreach ($groups as $group) {
346
				$groupItem = $groupManager->get($group);
347
				if ($groupItem instanceof \OCP\IGroup) {
348
					$groupsList[] = $groupManager->get($group);
349
				}
350
			}
351
			$appManager->enableAppForGroups($app, $groupsList);
352
		} else {
353
			$appManager->enableApp($app);
354
		}
355
356
		$info = self::getAppInfo($app);
357 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
358
			$appPath = self::getAppPath($app);
359
			self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 358 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...
360
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
361
		}
362
	}
363
364
	/**
365
	 * @param string $app
366
	 * @return int
367
	 */
368
	private static function downloadApp($app) {
369
		$ocsClient = new OCSClient(
370
			\OC::$server->getHTTPClientService(),
371
			\OC::$server->getConfig(),
372
			\OC::$server->getLogger()
373
		);
374
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
375
		$download = $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion());
376
		if(isset($download['downloadlink']) and $download['downloadlink']!='') {
377
			// Replace spaces in download link without encoding entire URL
378
			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
379
			$info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData);
380
			$app = Installer::installApp($info);
381
		}
382
		return $app;
383
	}
384
385
	/**
386
	 * @param string $app
387
	 * @return bool
388
	 */
389
	public static function removeApp($app) {
390
		if (self::isShipped($app)) {
391
			return false;
392
		}
393
394
		return Installer::removeApp($app);
395
	}
396
397
	/**
398
	 * This function set an app as disabled in appconfig.
399
	 *
400
	 * @param string $app app
401
	 * @throws Exception
402
	 */
403
	public static function disable($app) {
404
		// Convert OCS ID to regular application identifier
405
		if(self::getInternalAppIdByOcs($app) !== false) {
406
			$app = self::getInternalAppIdByOcs($app);
407
		}
408
409
		// flush
410
		self::$enabledAppsCache = array();
411
412
		// run uninstall steps
413
		$appData = OC_App::getAppInfo($app);
414
		if (!is_null($appData)) {
415
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
416
		}
417
418
		// emit disable hook - needed anymore ?
419
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
420
421
		// finally disable it
422
		$appManager = \OC::$server->getAppManager();
423
		$appManager->disableApp($app);
424
	}
425
426
	/**
427
	 * Returns the Settings Navigation
428
	 *
429
	 * @return string[]
430
	 *
431
	 * This function returns an array containing all settings pages added. The
432
	 * entries are sorted by the key 'order' ascending.
433
	 */
434
	public static function getSettingsNavigation() {
435
		$l = \OC::$server->getL10N('lib');
436
		$urlGenerator = \OC::$server->getURLGenerator();
437
438
		$settings = array();
439
		// by default, settings only contain the help menu
440
		if (\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true)) {
441
			$settings = array(
442
				array(
443
					"id" => "help",
444
					"order" => 4,
445
					"href" => $urlGenerator->linkToRoute('settings_help'),
446
					"name" => $l->t("Help"),
447
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
448
				)
449
			);
450
		}
451
452
		// if the user is logged-in
453
		if (OC_User::isLoggedIn()) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_User::isLoggedIn() has been deprecated with message: use \OC::$server->getUserSession()->isLoggedIn()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
454
			// personal menu
455
			$settings[] = array(
456
				"id" => "personal",
457
				"order" => 1,
458
				"href" => $urlGenerator->linkToRoute('settings_personal'),
459
				"name" => $l->t("Personal"),
460
				"icon" => $urlGenerator->imagePath("settings", "personal.svg")
461
			);
462
463
			//SubAdmins are also allowed to access user management
464
			$userObject = \OC::$server->getUserSession()->getUser();
465
			$isSubAdmin = false;
466
			if($userObject !== null) {
467
				$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
468
			}
469
			if ($isSubAdmin) {
470
				// admin users menu
471
				$settings[] = array(
472
					"id" => "core_users",
473
					"order" => 3,
474
					"href" => $urlGenerator->linkToRoute('settings_users'),
475
					"name" => $l->t("Users"),
476
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
477
				);
478
			}
479
480
			// if the user is an admin
481
			if (OC_User::isAdminUser(OC_User::getUser())) {
482
				// admin settings
483
				$settings[] = array(
484
					"id" => "admin",
485
					"order" => 2,
486
					"href" => $urlGenerator->linkToRoute('settings.AdminSettings.index'),
487
					"name" => $l->t("Admin"),
488
					"icon" => $urlGenerator->imagePath("settings", "admin.svg")
489
				);
490
			}
491
		}
492
493
		$navigation = self::proceedNavigation($settings);
494
		return $navigation;
495
	}
496
497
	// This is private as well. It simply works, so don't ask for more details
498
	private static function proceedNavigation($list) {
499
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
500
		foreach ($list as &$navEntry) {
501
			if ($navEntry['id'] == $activeApp) {
502
				$navEntry['active'] = true;
503
			} else {
504
				$navEntry['active'] = false;
505
			}
506
		}
507
		unset($navEntry);
508
509
		usort($list, function($a, $b) {
510
			if (isset($a['order']) && isset($b['order'])) {
511
				return ($a['order'] < $b['order']) ? -1 : 1;
512
			} else if (isset($a['order']) || isset($b['order'])) {
513
				return isset($a['order']) ? -1 : 1;
514
			} else {
515
				return ($a['name'] < $b['name']) ? -1 : 1;
516
			}
517
		});
518
519
		return $list;
520
	}
521
522
	/**
523
	 * Get the path where to install apps
524
	 *
525
	 * @return string|false
526
	 */
527
	public static function getInstallPath() {
528
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
529
			return false;
530
		}
531
532
		foreach (OC::$APPSROOTS as $dir) {
533
			if (isset($dir['writable']) && $dir['writable'] === true) {
534
				return $dir['path'];
535
			}
536
		}
537
538
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
539
		return null;
540
	}
541
542
543
	/**
544
	 * search for an app in all app-directories
545
	 *
546
	 * @param string $appId
547
	 * @return false|string
548
	 */
549
	protected static function findAppInDirectories($appId) {
550
		$sanitizedAppId = self::cleanAppId($appId);
551
		if($sanitizedAppId !== $appId) {
552
			return false;
553
		}
554
		static $app_dir = array();
555
556
		if (isset($app_dir[$appId])) {
557
			return $app_dir[$appId];
558
		}
559
560
		$possibleApps = array();
561
		foreach (OC::$APPSROOTS as $dir) {
562
			if (file_exists($dir['path'] . '/' . $appId)) {
563
				$possibleApps[] = $dir;
564
			}
565
		}
566
567
		if (empty($possibleApps)) {
568
			return false;
569
		} elseif (count($possibleApps) === 1) {
570
			$dir = array_shift($possibleApps);
571
			$app_dir[$appId] = $dir;
572
			return $dir;
573
		} else {
574
			$versionToLoad = array();
575
			foreach ($possibleApps as $possibleApp) {
576
				$version = self::getAppVersionByPath($possibleApp['path']);
577
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
578
					$versionToLoad = array(
579
						'dir' => $possibleApp,
580
						'version' => $version,
581
					);
582
				}
583
			}
584
			$app_dir[$appId] = $versionToLoad['dir'];
585
			return $versionToLoad['dir'];
586
			//TODO - write test
587
		}
588
	}
589
590
	/**
591
	 * Get the directory for the given app.
592
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
593
	 *
594
	 * @param string $appId
595
	 * @return string|false
596
	 */
597
	public static function getAppPath($appId) {
598
		if ($appId === null || trim($appId) === '') {
599
			return false;
600
		}
601
602
		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...
603
			return $dir['path'] . '/' . $appId;
604
		}
605
		return false;
606
	}
607
608
609
	/**
610
	 * check if an app's directory is writable
611
	 *
612
	 * @param string $appId
613
	 * @return bool
614
	 */
615
	public static function isAppDirWritable($appId) {
616
		$path = self::getAppPath($appId);
617
		return ($path !== false) ? is_writable($path) : false;
618
	}
619
620
	/**
621
	 * Get the path for the given app on the access
622
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
623
	 *
624
	 * @param string $appId
625
	 * @return string|false
626
	 */
627
	public static function getAppWebPath($appId) {
628
		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...
629
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
630
		}
631
		return false;
632
	}
633
634
	/**
635
	 * get the last version of the app from appinfo/info.xml
636
	 *
637
	 * @param string $appId
638
	 * @return string
639
	 */
640
	public static function getAppVersion($appId) {
641
		if (!isset(self::$appVersion[$appId])) {
642
			$file = self::getAppPath($appId);
643
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
644
		}
645
		return self::$appVersion[$appId];
646
	}
647
648
	/**
649
	 * get app's version based on it's path
650
	 *
651
	 * @param string $path
652
	 * @return string
653
	 */
654
	public static function getAppVersionByPath($path) {
655
		$infoFile = $path . '/appinfo/info.xml';
656
		$appData = self::getAppInfo($infoFile, true);
657
		return isset($appData['version']) ? $appData['version'] : '';
658
	}
659
660
661
	/**
662
	 * Read all app metadata from the info.xml file
663
	 *
664
	 * @param string $appId id of the app or the path of the info.xml file
665
	 * @param bool $path
666
	 * @param string $lang
667
	 * @return array|null
668
	 * @note all data is read from info.xml, not just pre-defined fields
669
	 */
670
	public static function getAppInfo($appId, $path = false, $lang = null) {
671
		if ($path) {
672
			$file = $appId;
673
		} else {
674
			if ($lang === null && isset(self::$appInfo[$appId])) {
675
				return self::$appInfo[$appId];
676
			}
677
			$appPath = self::getAppPath($appId);
678
			if($appPath === false) {
679
				return null;
680
			}
681
			$file = $appPath . '/appinfo/info.xml';
682
		}
683
684
		$parser = new \OC\App\InfoParser(\OC::$server->getURLGenerator());
685
		$data = $parser->parse($file);
0 ignored issues
show
Bug Compatibility introduced by
The expression $parser->parse($file); of type null|string|array adds the type string to the return on line 701 which is incompatible with the return type documented by OC_App::getAppInfo of type array|null.
Loading history...
686
687
		if (is_array($data)) {
688
			$data = OC_App::parseAppInfo($data, $lang);
689
		}
690
		if(isset($data['ocsid'])) {
691
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
692
			if($storedId !== '' && $storedId !== $data['ocsid']) {
693
				$data['ocsid'] = $storedId;
694
			}
695
		}
696
697
		if ($lang === null) {
698
			self::$appInfo[$appId] = $data;
699
		}
700
701
		return $data;
702
	}
703
704
	/**
705
	 * Returns the navigation
706
	 *
707
	 * @return array
708
	 *
709
	 * This function returns an array containing all entries added. The
710
	 * entries are sorted by the key 'order' ascending. Additional to the keys
711
	 * given for each app the following keys exist:
712
	 *   - active: boolean, signals if the user is on this navigation entry
713
	 */
714
	public static function getNavigation() {
715
		$entries = OC::$server->getNavigationManager()->getAll();
716
		$navigation = self::proceedNavigation($entries);
717
		return $navigation;
718
	}
719
720
	/**
721
	 * get the id of loaded app
722
	 *
723
	 * @return string
724
	 */
725
	public static function getCurrentApp() {
726
		$request = \OC::$server->getRequest();
727
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
728
		$topFolder = substr($script, 0, strpos($script, '/'));
729
		if (empty($topFolder)) {
730
			$path_info = $request->getPathInfo();
731
			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...
732
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
733
			}
734
		}
735
		if ($topFolder == 'apps') {
736
			$length = strlen($topFolder);
737
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
738
		} else {
739
			return $topFolder;
740
		}
741
	}
742
743
	/**
744
	 * @param string $type
745
	 * @return array
746
	 */
747
	public static function getForms($type) {
748
		$forms = array();
749
		switch ($type) {
750
			case 'admin':
751
				$source = self::$adminForms;
752
				break;
753
			case 'personal':
754
				$source = self::$personalForms;
755
				break;
756
			default:
757
				return array();
758
		}
759
		foreach ($source as $form) {
760
			$forms[] = include $form;
761
		}
762
		return $forms;
763
	}
764
765
	/**
766
	 * register an admin form to be shown
767
	 *
768
	 * @param string $app
769
	 * @param string $page
770
	 */
771
	public static function registerAdmin($app, $page) {
772
		self::$adminForms[] = $app . '/' . $page . '.php';
773
	}
774
775
	/**
776
	 * register a personal form to be shown
777
	 * @param string $app
778
	 * @param string $page
779
	 */
780
	public static function registerPersonal($app, $page) {
781
		self::$personalForms[] = $app . '/' . $page . '.php';
782
	}
783
784
	/**
785
	 * @param array $entry
786
	 */
787
	public static function registerLogIn(array $entry) {
788
		self::$altLogin[] = $entry;
789
	}
790
791
	/**
792
	 * @return array
793
	 */
794
	public static function getAlternativeLogIns() {
795
		return self::$altLogin;
796
	}
797
798
	/**
799
	 * get a list of all apps in the apps folder
800
	 *
801
	 * @return array an array of app names (string IDs)
802
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
803
	 */
804
	public static function getAllApps() {
805
806
		$apps = array();
807
808
		foreach (OC::$APPSROOTS as $apps_dir) {
809
			if (!is_readable($apps_dir['path'])) {
810
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
811
				continue;
812
			}
813
			$dh = opendir($apps_dir['path']);
814
815
			if (is_resource($dh)) {
816
				while (($file = readdir($dh)) !== false) {
817
818
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
819
820
						$apps[] = $file;
821
					}
822
				}
823
			}
824
		}
825
826
		return $apps;
827
	}
828
829
	/**
830
	 * List all apps, this is used in apps.php
831
	 *
832
	 * @param bool $onlyLocal
833
	 * @param bool $includeUpdateInfo Should we check whether there is an update
834
	 *                                in the app store?
835
	 * @param OCSClient $ocsClient
836
	 * @return array
837
	 */
838
	public static function listAllApps($onlyLocal = false,
839
									   $includeUpdateInfo = true,
840
									   OCSClient $ocsClient) {
841
		$installedApps = OC_App::getAllApps();
842
843
		//TODO which apps do we want to blacklist and how do we integrate
844
		// blacklisting with the multi apps folder feature?
845
846
		//we don't want to show configuration for these
847
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
848
		$appList = array();
849
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
850
851
		foreach ($installedApps as $app) {
852
			if (array_search($app, $blacklist) === false) {
853
854
				$info = OC_App::getAppInfo($app, false, $langCode);
855
				if (!is_array($info)) {
856
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
857
					continue;
858
				}
859
860
				if (!isset($info['name'])) {
861
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
862
					continue;
863
				}
864
865
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::getValue() has been deprecated with message: 8.0.0 use method getAppValue of \OCP\IConfig This function gets a value from the appconfig table. If the key does
not exist the default value will be returned

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
866
				$info['groups'] = null;
867
				if ($enabled === 'yes') {
868
					$active = true;
869
				} else if ($enabled === 'no') {
870
					$active = false;
871
				} else {
872
					$active = true;
873
					$info['groups'] = $enabled;
874
				}
875
876
				$info['active'] = $active;
877
878
				if (self::isShipped($app)) {
879
					$info['internal'] = true;
880
					$info['level'] = self::officialApp;
881
					$info['removable'] = false;
882
				} else {
883
					$info['internal'] = false;
884
					$info['removable'] = true;
885
				}
886
887
				$info['update'] = ($includeUpdateInfo) ? Installer::isUpdateAvailable($app) : null;
888
889
				$appPath = self::getAppPath($app);
890
				if($appPath !== false) {
891
					$appIcon = $appPath . '/img/' . $app . '.svg';
892
					if (file_exists($appIcon)) {
893
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
894
						$info['previewAsIcon'] = true;
895
					} else {
896
						$appIcon = $appPath . '/img/app.svg';
897
						if (file_exists($appIcon)) {
898
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
899
							$info['previewAsIcon'] = true;
900
						}
901
					}
902
				}
903
				$info['version'] = OC_App::getAppVersion($app);
904
				$appList[] = $info;
905
			}
906
		}
907
		if ($onlyLocal) {
908
			$remoteApps = [];
909
		} else {
910
			$remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient);
911
		}
912
		if ($remoteApps) {
913
			// Remove duplicates
914
			foreach ($appList as $app) {
915
				foreach ($remoteApps AS $key => $remote) {
916
					if ($app['name'] === $remote['name'] ||
917
						(isset($app['ocsid']) &&
918
							$app['ocsid'] === $remote['id'])
919
					) {
920
						unset($remoteApps[$key]);
921
					}
922
				}
923
			}
924
			$combinedApps = array_merge($appList, $remoteApps);
925
		} else {
926
			$combinedApps = $appList;
927
		}
928
929
		return $combinedApps;
930
	}
931
932
	/**
933
	 * Returns the internal app ID or false
934
	 * @param string $ocsID
935
	 * @return string|false
936
	 */
937
	public static function getInternalAppIdByOcs($ocsID) {
938
		if(is_numeric($ocsID)) {
939
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
940
			if(array_search($ocsID, $idArray)) {
941
				return array_search($ocsID, $idArray);
942
			}
943
		}
944
		return false;
945
	}
946
947
	/**
948
	 * Get a list of all apps on the appstore
949
	 * @param string $filter
950
	 * @param string|null $category
951
	 * @param OCSClient $ocsClient
952
	 * @return array|bool  multi-dimensional array of apps.
953
	 *                     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
954
	 */
955
	public static function getAppstoreApps($filter = 'approved',
956
										   $category = null,
957
										   OCSClient $ocsClient) {
958
		$categories = [$category];
959
960
		if (is_null($category)) {
961
			$categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion());
962
			if (is_array($categoryNames)) {
963
				// Check that categories of apps were retrieved correctly
964
				if (!$categories = array_keys($categoryNames)) {
965
					return false;
966
				}
967
			} else {
968
				return false;
969
			}
970
		}
971
972
		$page = 0;
973
		$remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion());
974
		$apps = [];
975
		$i = 0;
976
		$l = \OC::$server->getL10N('core');
977
		foreach ($remoteApps as $app) {
978
			$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
979
			// enhance app info (for example the description)
980
			$apps[$i] = OC_App::parseAppInfo($app);
981
			$apps[$i]['author'] = $app['personid'];
982
			$apps[$i]['ocs_id'] = $app['id'];
983
			$apps[$i]['internal'] = 0;
984
			$apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
985
			$apps[$i]['update'] = false;
986
			$apps[$i]['groups'] = false;
987
			$apps[$i]['score'] = $app['score'];
988
			$apps[$i]['removable'] = false;
989
			if ($app['label'] == 'recommended') {
990
				$apps[$i]['internallabel'] = (string)$l->t('Recommended');
991
				$apps[$i]['internalclass'] = 'recommendedapp';
992
			}
993
994
			// Apps from the appstore are always assumed to be compatible with the
995
			// the current release as the initial filtering is done on the appstore
996
			$apps[$i]['dependencies']['owncloud']['@attributes']['min-version'] = implode('.', \OCP\Util::getVersion());
997
			$apps[$i]['dependencies']['owncloud']['@attributes']['max-version'] = implode('.', \OCP\Util::getVersion());
998
999
			$i++;
1000
		}
1001
1002
1003
1004
		if (empty($apps)) {
1005
			return false;
1006
		} else {
1007
			return $apps;
1008
		}
1009
	}
1010
1011
	public static function shouldUpgrade($app) {
1012
		$versions = self::getAppVersions();
1013
		$currentVersion = OC_App::getAppVersion($app);
1014
		if ($currentVersion && isset($versions[$app])) {
1015
			$installedVersion = $versions[$app];
1016
			if (!version_compare($currentVersion, $installedVersion, '=')) {
1017
				return true;
1018
			}
1019
		}
1020
		return false;
1021
	}
1022
1023
	/**
1024
	 * Adjust the number of version parts of $version1 to match
1025
	 * the number of version parts of $version2.
1026
	 *
1027
	 * @param string $version1 version to adjust
1028
	 * @param string $version2 version to take the number of parts from
1029
	 * @return string shortened $version1
1030
	 */
1031
	private static function adjustVersionParts($version1, $version2) {
1032
		$version1 = explode('.', $version1);
1033
		$version2 = explode('.', $version2);
1034
		// reduce $version1 to match the number of parts in $version2
1035
		while (count($version1) > count($version2)) {
1036
			array_pop($version1);
1037
		}
1038
		// if $version1 does not have enough parts, add some
1039
		while (count($version1) < count($version2)) {
1040
			$version1[] = '0';
1041
		}
1042
		return implode('.', $version1);
1043
	}
1044
1045
	/**
1046
	 * Check whether the current ownCloud version matches the given
1047
	 * application's version requirements.
1048
	 *
1049
	 * The comparison is made based on the number of parts that the
1050
	 * app info version has. For example for ownCloud 6.0.3 if the
1051
	 * app info version is expecting version 6.0, the comparison is
1052
	 * made on the first two parts of the ownCloud version.
1053
	 * This means that it's possible to specify "requiremin" => 6
1054
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
1055
	 *
1056
	 * @param string $ocVersion ownCloud version to check against
1057
	 * @param array $appInfo app info (from xml)
1058
	 *
1059
	 * @return boolean true if compatible, otherwise false
1060
	 */
1061
	public static function isAppCompatible($ocVersion, $appInfo) {
1062
		$requireMin = '';
1063
		$requireMax = '';
1064
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
1065
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1066
		} else if (isset($appInfo['requiremin'])) {
1067
			$requireMin = $appInfo['requiremin'];
1068
		} else if (isset($appInfo['require'])) {
1069
			$requireMin = $appInfo['require'];
1070
		}
1071
1072
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
1073
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1074
		} else if (isset($appInfo['requiremax'])) {
1075
			$requireMax = $appInfo['requiremax'];
1076
		}
1077
1078
		if (is_array($ocVersion)) {
1079
			$ocVersion = implode('.', $ocVersion);
1080
		}
1081
1082 View Code Duplication
		if (!empty($requireMin)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1083
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1084
		) {
1085
1086
			return false;
1087
		}
1088
1089 View Code Duplication
		if (!empty($requireMax)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1090
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1091
		) {
1092
			return false;
1093
		}
1094
1095
		return true;
1096
	}
1097
1098
	/**
1099
	 * get the installed version of all apps
1100
	 */
1101
	public static function getAppVersions() {
1102
		static $versions;
1103
1104
		if(!$versions) {
1105
			$appConfig = \OC::$server->getAppConfig();
1106
			$versions = $appConfig->getValues(false, 'installed_version');
1107
		}
1108
		return $versions;
1109
	}
1110
1111
	/**
1112
	 * @param string $app
1113
	 * @return bool
1114
	 * @throws Exception if app is not compatible with this version of ownCloud
1115
	 * @throws Exception if no app-name was specified
1116
	 */
1117
	public static function installApp($app) {
1118
		$appName = $app; // $app will be overwritten, preserve name for error logging
1119
		$l = \OC::$server->getL10N('core');
1120
		$config = \OC::$server->getConfig();
1121
		$ocsClient = new OCSClient(
1122
			\OC::$server->getHTTPClientService(),
1123
			$config,
1124
			\OC::$server->getLogger()
1125
		);
1126
		$appData = $ocsClient->getApplication($app, \OCP\Util::getVersion());
1127
1128
		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
1129
		if (!is_numeric($app)) {
1130
			$shippedVersion = self::getAppVersion($app);
1131
			if ($appData && version_compare($shippedVersion, $appData['version'], '<')) {
1132
				$app = self::downloadApp($app);
1133
			} else {
1134
				$app = Installer::installShippedApp($app);
1135
			}
1136
		} else {
1137
			// Maybe the app is already installed - compare the version in this
1138
			// case and use the local already installed one.
1139
			// FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me.
1140
			$internalAppId = self::getInternalAppIdByOcs($app);
1141
			if($internalAppId !== false) {
1142
				if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) {
1143
					$app = self::downloadApp($app);
1144
				} else {
1145
					self::enable($internalAppId);
1146
					$app = $internalAppId;
1147
				}
1148
			} else {
1149
				$app = self::downloadApp($app);
1150
			}
1151
		}
1152
1153
		if ($app !== false) {
1154
			// check if the app is compatible with this version of ownCloud
1155
			$info = self::getAppInfo($app);
1156
			if(!is_array($info)) {
1157
				throw new \Exception(
1158
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1159
						[$info['name']]
1160
					)
1161
				);
1162
			}
1163
1164
			$version = \OCP\Util::getVersion();
1165
			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...
1166
				throw new \Exception(
1167
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1168
						array($info['name'])
1169
					)
1170
				);
1171
			}
1172
1173
			// check for required dependencies
1174
			$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1175
			$missing = $dependencyAnalyzer->analyze($info);
1176
			if (!empty($missing)) {
1177
				$missingMsg = join(PHP_EOL, $missing);
1178
				throw new \Exception(
1179
					$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1180
						array($info['name'], $missingMsg)
1181
					)
1182
				);
1183
			}
1184
1185
			$config->setAppValue($app, 'enabled', 'yes');
1186
			if (isset($appData['id'])) {
1187
				$config->setAppValue($app, 'ocsid', $appData['id']);
1188
			}
1189
1190 View Code Duplication
			if(isset($info['settings']) && is_array($info['settings'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1191
				$appPath = self::getAppPath($app);
1192
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1191 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...
1193
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1194
			}
1195
1196
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1197
		} else {
1198
			if(empty($appName) ) {
1199
				throw new \Exception($l->t("No app name specified"));
1200
			} else {
1201
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
0 ignored issues
show
Documentation introduced by
$appName is of type string, but the function expects a array.

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...
1202
			}
1203
		}
1204
1205
		return $app;
1206
	}
1207
1208
	/**
1209
	 * update the database for the app and call the update script
1210
	 *
1211
	 * @param string $appId
1212
	 * @return bool
1213
	 */
1214
	public static function updateApp($appId) {
1215
		$appPath = self::getAppPath($appId);
1216
		if($appPath === false) {
1217
			return false;
1218
		}
1219
		$appData = self::getAppInfo($appId);
1220
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1221
		if (file_exists($appPath . '/appinfo/database.xml')) {
1222
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1223
		}
1224
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1225
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1226
		unset(self::$appVersion[$appId]);
1227
		// run upgrade code
1228
		if (file_exists($appPath . '/appinfo/update.php')) {
1229
			self::loadApp($appId, false);
1230
			include $appPath . '/appinfo/update.php';
1231
		}
1232
		self::setupBackgroundJobs($appData['background-jobs']);
1233 View Code Duplication
		if(isset($appData['settings']) && is_array($appData['settings'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1234
			$appPath = self::getAppPath($appId);
1235
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 1234 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...
1236
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1237
		}
1238
1239
		//set remote/public handlers
1240
		if (array_key_exists('ocsid', $appData)) {
1241
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1242 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1243
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1244
		}
1245
		foreach ($appData['remote'] as $name => $path) {
1246
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1247
		}
1248 View Code Duplication
		foreach ($appData['public'] as $name => $path) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1249
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1250
		}
1251
1252
		self::setAppTypes($appId);
1253
1254
		$version = \OC_App::getAppVersion($appId);
1255
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1256
1257
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1258
			ManagerEvent::EVENT_APP_UPDATE, $appId
1259
		));
1260
1261
		return true;
1262
	}
1263
1264
	/**
1265
	 * @param string $appId
1266
	 * @param string[] $steps
1267
	 * @throws \OC\NeedsUpdateException
1268
	 */
1269
	public static function executeRepairSteps($appId, array $steps) {
1270
		if (empty($steps)) {
1271
			return;
1272
		}
1273
		// load the app
1274
		self::loadApp($appId, false);
1275
1276
		$dispatcher = OC::$server->getEventDispatcher();
1277
1278
		// load the steps
1279
		$r = new Repair([], $dispatcher);
1280
		foreach ($steps as $step) {
1281
			try {
1282
				$r->addStep($step);
1283
			} catch (Exception $ex) {
1284
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1285
				\OC::$server->getLogger()->logException($ex);
1286
			}
1287
		}
1288
		// run the steps
1289
		$r->run();
1290
	}
1291
1292
	public static function setupBackgroundJobs(array $jobs) {
1293
		$queue = \OC::$server->getJobList();
1294
		foreach ($jobs as $job) {
1295
			$queue->add($job);
1296
		}
1297
	}
1298
1299
	/**
1300
	 * @param string $appId
1301
	 * @param string[] $steps
1302
	 */
1303
	private static function setupLiveMigrations($appId, array $steps) {
1304
		$queue = \OC::$server->getJobList();
1305
		foreach ($steps as $step) {
1306
			$queue->add('OC\Migration\BackgroundRepair', [
1307
				'app' => $appId,
1308
				'step' => $step]);
1309
		}
1310
	}
1311
1312
	/**
1313
	 * @param string $appId
1314
	 * @return \OC\Files\View|false
1315
	 */
1316
	public static function getStorage($appId) {
1317
		if (OC_App::isEnabled($appId)) { //sanity check
1318
			if (OC_User::isLoggedIn()) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_User::isLoggedIn() has been deprecated with message: use \OC::$server->getUserSession()->isLoggedIn()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1319
				$view = new \OC\Files\View('/' . OC_User::getUser());
1320
				if (!$view->file_exists($appId)) {
1321
					$view->mkdir($appId);
1322
				}
1323
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1324
			} else {
1325
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1326
				return false;
1327
			}
1328
		} else {
1329
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1330
			return false;
1331
		}
1332
	}
1333
1334
	protected static function findBestL10NOption($options, $lang) {
1335
		$fallback = $similarLangFallback = $englishFallback = false;
1336
1337
		$lang = strtolower($lang);
1338
		$similarLang = $lang;
1339
		if (strpos($similarLang, '_')) {
1340
			// For "de_DE" we want to find "de" and the other way around
1341
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1342
		}
1343
1344
		foreach ($options as $option) {
1345
			if (is_array($option)) {
1346
				if ($fallback === false) {
1347
					$fallback = $option['@value'];
1348
				}
1349
1350
				if (!isset($option['@attributes']['lang'])) {
1351
					continue;
1352
				}
1353
1354
				$attributeLang = strtolower($option['@attributes']['lang']);
1355
				if ($attributeLang === $lang) {
1356
					return $option['@value'];
1357
				}
1358
1359
				if ($attributeLang === $similarLang) {
1360
					$similarLangFallback = $option['@value'];
1361
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1362
					if ($similarLangFallback === false) {
1363
						$similarLangFallback =  $option['@value'];
1364
					}
1365
				}
1366
			} else {
1367
				$englishFallback = $option;
1368
			}
1369
		}
1370
1371
		if ($similarLangFallback !== false) {
1372
			return $similarLangFallback;
1373
		} else if ($englishFallback !== false) {
1374
			return $englishFallback;
1375
		}
1376
		return (string) $fallback;
1377
	}
1378
1379
	/**
1380
	 * parses the app data array and enhanced the 'description' value
1381
	 *
1382
	 * @param array $data the app data
1383
	 * @param string $lang
1384
	 * @return array improved app data
1385
	 */
1386
	public static function parseAppInfo(array $data, $lang = null) {
1387
1388 View Code Duplication
		if ($lang && isset($data['name']) && is_array($data['name'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1389
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1390
		}
1391 View Code Duplication
		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1392
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1393
		}
1394 View Code Duplication
		if ($lang && isset($data['description']) && is_array($data['description'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1395
			$data['description'] = self::findBestL10NOption($data['description'], $lang);
1396
		}
1397
1398
		// just modify the description if it is available
1399
		// otherwise this will create a $data element with an empty 'description'
1400
		if (isset($data['description'])) {
1401
			if (is_string($data['description'])) {
1402
				// sometimes the description contains line breaks and they are then also
1403
				// shown in this way in the app management which isn't wanted as HTML
1404
				// manages line breaks itself
1405
1406
				// first of all we split on empty lines
1407
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1408
1409
				$result = [];
1410
				foreach ($paragraphs as $value) {
1411
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1412
					// with a single space - also trims whitespace
1413
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1414
				}
1415
1416
				// join the single paragraphs with a empty line in between
1417
				$data['description'] = implode("\n\n", $result);
1418
1419
			} else {
1420
				$data['description'] = '';
1421
			}
1422
		}
1423
1424
		return $data;
1425
	}
1426
}
1427