Completed
Push — stable8 ( c45eda...dba072 )
by
unknown
15:55
created

OC_App   F

Complexity

Total Complexity 208

Size/Duplication

Total Lines 1213
Duplicated Lines 3.96 %

Coupling/Cohesion

Components 2
Dependencies 27

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 48
loc 1213
rs 0.5217
wmc 208
lcom 2
cbo 27

48 Methods

Rating   Name   Duplication   Size   Complexity  
B loadApps() 0 17 6
B loadApp() 0 18 5
C updateApp() 6 33 8
B parseAppInfo() 0 26 3
A cleanAppId() 0 3 1
A requireAppFile() 0 4 1
A isType() 0 12 4
A getAppTypes() 0 12 3
A setAppTypes() 0 11 2
A isShipped() 0 8 3
C getEnabledApps() 0 49 15
A isEnabled() 0 7 2
A enable() 0 13 3
A removeApp() 0 7 2
A disable() 0 15 3
A addNavigationEntry() 0 4 1
A setActiveNavigationEntry() 0 4 1
A getAppNavigationEntries() 0 8 2
A getActiveNavigationEntry() 0 3 1
C getSettingsNavigation() 30 71 7
A proceedNavigation() 0 15 3
B getInstallPath() 0 14 5
D findAppInDirectories() 0 36 9
A getAppPath() 0 10 4
A isAppDirWritable() 0 4 2
A getAppWebPath() 0 6 2
A getAppVersion() 0 4 2
A getAppVersionByPath() 0 10 3
A getAppInfo() 0 21 4
A getNavigation() 0 5 1
A getCurrentApp() 0 16 4
B getForms() 0 20 5
A registerSettings() 0 3 1
A registerAdmin() 0 3 1
A registerPersonal() 0 3 1
A registerLogIn() 0 3 1
A getAlternativeLogIns() 0 3 1
C getAllApps() 0 27 7
D listAllApps() 0 111 28
A getInternalAppIdByOcs() 0 9 3
A shouldUpgrade() 0 11 4
A adjustVersionParts() 0 13 3
C isAppCompatible() 12 37 11
A getAppVersions() 0 18 4
C installApp() 0 65 11
A getStorage() 0 17 4
A downloadApp() 0 11 3
C getAppstoreApps() 0 45 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like OC_App often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OC_App, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * ownCloud
4
 *
5
 * @author Frank Karlitschek
6
 * @copyright 2012 Frank Karlitschek <[email protected]>
7
 *
8
 * @author Jakob Sack
9
 * @copyright 2012 Jakob Sack <[email protected]>
10
 *
11
 * @author Georg Ehrke
12
 * @copyright 2014 Georg Ehrke <[email protected]>
13
 *
14
 * This library is free software; you can redistribute it and/or
15
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
16
 * License as published by the Free Software Foundation; either
17
 * version 3 of the License, or any later version.
18
 *
19
 * This library is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public
25
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
use OC\App\DependencyAnalyzer;
29
use OC\App\Platform;
30
31
/**
32
 * This class manages the apps. It allows them to register and integrate in the
33
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
34
 * upgrading and removing apps.
35
 */
36
class OC_App {
37
	static private $settingsForms = array();
38
	static private $adminForms = array();
39
	static private $personalForms = array();
40
	static private $appInfo = array();
41
	static private $appTypes = array();
42
	static private $loadedApps = array();
43
	static private $checkedApps = array();
44
	static private $altLogin = array();
45
46
	/**
47
	 * clean the appId
48
	 * @param string|boolean $app AppId that needs to be cleaned
49
	 * @return string
50
	 */
51
	public static function cleanAppId($app) {
52
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
53
	}
54
55
	/**
56
	 * loads all apps
57
	 * @param array $types
58
	 * @return bool
59
	 *
60
	 * This function walks through the ownCloud directory and loads all apps
61
	 * it can find. A directory contains an app if the file /appinfo/app.php
62
	 * exists.
63
	 *
64
	 * if $types is set, only apps of those types will be loaded
65
	 */
66
	public static function loadApps($types = null) {
67
		if (OC_Config::getValue('maintenance', false)) {
68
			return false;
69
		}
70
		// Load the enabled apps here
71
		$apps = self::getEnabledApps();
72
		// prevent app.php from printing output
73
		ob_start();
74
		foreach ($apps as $app) {
75
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
76
				self::loadApp($app);
77
			}
78
		}
79
		ob_end_clean();
80
81
		return true;
82
	}
83
84
	/**
85
	 * load a single app
86
	 *
87
	 * @param string $app
88
	 * @param bool $checkUpgrade whether an upgrade check should be done
89
	 * @throws \OC\NeedsUpdateException
90
	 */
91
	public static function loadApp($app, $checkUpgrade = true) {
92
		self::$loadedApps[] = $app;
93
		if (is_file(self::getAppPath($app) . '/appinfo/app.php')) {
94
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
95
			if ($checkUpgrade and self::shouldUpgrade($app)) {
96
				throw new \OC\NeedsUpdateException();
97
			}
98
			self::requireAppFile($app);
99
			if (self::isType($app, array('authentication'))) {
100
				// since authentication apps affect the "is app enabled for group" check,
101
				// the enabled apps cache needs to be cleared to make sure that the
102
				// next time getEnableApps() is called it will also include apps that were
103
				// enabled for groups
104
				self::$enabledAppsCache = array();
105
			}
106
			\OC::$server->getEventLogger()->end('load_app_' . $app);
107
		}
108
	}
109
110
	/**
111
	 * Load app.php from the given app
112
	 *
113
	 * @param string $app app name
114
	 */
115
	private static function requireAppFile($app) {
116
		// encapsulated here to avoid variable scope conflicts
117
		require_once $app . '/appinfo/app.php';
118
	}
119
120
	/**
121
	 * check if an app is of a specific type
122
	 *
123
	 * @param string $app
124
	 * @param string|array $types
125
	 * @return bool
126
	 */
127
	public static function isType($app, $types) {
128
		if (is_string($types)) {
129
			$types = array($types);
130
		}
131
		$appTypes = self::getAppTypes($app);
132
		foreach ($types as $type) {
133
			if (array_search($type, $appTypes) !== false) {
134
				return true;
135
			}
136
		}
137
		return false;
138
	}
139
140
	/**
141
	 * get the types of an app
142
	 *
143
	 * @param string $app
144
	 * @return array
145
	 */
146
	private static function getAppTypes($app) {
147
		//load the cache
148
		if (count(self::$appTypes) == 0) {
149
			self::$appTypes = OC_Appconfig::getValues(false, 'types');
150
		}
151
152
		if (isset(self::$appTypes[$app])) {
153
			return explode(',', self::$appTypes[$app]);
154
		} else {
155
			return array();
156
		}
157
	}
158
159
	/**
160
	 * read app types from info.xml and cache them in the database
161
	 */
162
	public static function setAppTypes($app) {
163
		$appData = self::getAppInfo($app);
164
165
		if (isset($appData['types'])) {
166
			$appTypes = implode(',', $appData['types']);
167
		} else {
168
			$appTypes = '';
169
		}
170
171
		OC_Appconfig::setValue($app, 'types', $appTypes);
172
	}
173
174
	/**
175
	 * check if app is shipped
176
	 *
177
	 * @param string $appId the id of the app to check
178
	 * @return bool
179
	 *
180
	 * Check if an app that is installed is a shipped app or installed from the appstore.
181
	 */
182
	public static function isShipped($appId) {
183
		$info = self::getAppInfo($appId);
184
		if (isset($info['shipped']) && $info['shipped'] == 'true') {
185
			return true;
186
		} else {
187
			return false;
188
		}
189
	}
190
191
	/**
192
	 * get all enabled apps
193
	 */
194
	protected static $enabledAppsCache = array();
195
196
	/**
197
	 * Returns apps enabled for the current user.
198
	 *
199
	 * @param bool $forceRefresh whether to refresh the cache
200
	 * @param bool $all whether to return apps for all users, not only the
201
	 * currently logged in one
202
	 * @return array
203
	 */
204
	public static function getEnabledApps($forceRefresh = false, $all = false) {
205
		if (!OC_Config::getValue('installed', false)) {
206
			return array();
207
		}
208
		// in incognito mode or when logged out, $user will be false,
209
		// which is also the case during an upgrade
210
		$user = null;
211
		if (!$all) {
212
			$user = \OC_User::getUser();
213
		}
214
		if (is_string($user) && !$forceRefresh && !empty(self::$enabledAppsCache)) {
215
			return self::$enabledAppsCache;
216
		}
217
		$apps = array();
218
		$appConfig = \OC::$server->getAppConfig();
219
		$appStatus = $appConfig->getValues(false, 'enabled');
220
		foreach ($appStatus as $app => $enabled) {
221
			if ($app === 'files') {
222
				continue;
223
			}
224
			if ($enabled === 'yes') {
225
				$apps[] = $app;
226
			} else if ($enabled !== 'no') {
227
				$groups = json_decode($enabled);
228
				if (is_array($groups)) {
229
					if (is_string($user)) {
230
						foreach ($groups as $group) {
231
							if (\OC_Group::inGroup($user, $group)) {
232
								$apps[] = $app;
233
								break;
234
							}
235
						}
236
					} else {
237
						// global, consider app as enabled
238
						$apps[] = $app;
239
					}
240
				}
241
			}
242
		}
243
		sort($apps);
244
		array_unshift($apps, 'files');
245
		// Only cache the app list, when the user is logged in.
246
		// Otherwise we cache the list with disabled apps, although
247
		// the apps are enabled for the user after he logged in.
248
		if ($user) {
249
			self::$enabledAppsCache = $apps;
250
		}
251
		return $apps;
252
	}
253
254
	/**
255
	 * checks whether or not an app is enabled
256
	 * @param string $app app
257
	 * @return bool
258
	 *
259
	 * This function checks whether or not an app is enabled.
260
	 */
261
	public static function isEnabled($app) {
262
		if ('files' == $app) {
263
			return true;
264
		}
265
		$enabledApps = self::getEnabledApps();
266
		return in_array($app, $enabledApps);
267
	}
268
269
	/**
270
	 * enables an app
271
	 * @param mixed $app app
272
	 * @param array $groups (optional) when set, only these groups will have access to the app
273
	 * @throws \Exception
274
	 * @return void
275
	 *
276
	 * This function set an app as enabled in appconfig.
277
	 */
278
	public static function enable($app, $groups = null) {
279
		self::$enabledAppsCache = array(); // flush
280
281
		if (!OC_Installer::isInstalled($app)) {
282
			$app = self::installApp($app);
283
		}
284
285
		if (!is_null($groups)) {
286
			OC_Appconfig::setValue($app, 'enabled', json_encode($groups));
287
		}else{
288
			OC_Appconfig::setValue($app, 'enabled', 'yes');
289
		}
290
	}
291
292
	/**
293
	 * @param string $app
294
	 * @return int
295
	 */
296
	public static function downloadApp($app) {
297
		$appData=OC_OCSClient::getApplication($app, \OC_Util::getVersion());
298
		$download=OC_OCSClient::getApplicationDownload($app, 1, \OC_Util::getVersion());
299
		if(isset($download['downloadlink']) and $download['downloadlink']!='') {
300
			// Replace spaces in download link without encoding entire URL
301
			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
302
			$info = array('source'=>'http', 'href'=>$download['downloadlink'], 'appdata'=>$appData);
303
			$app=OC_Installer::installApp($info);
304
		}
305
		return $app;
306
	}
307
308
	/**
309
	 * @param string $app
310
	 * @return bool
311
	 */
312
	public static function removeApp($app) {
313
		if (self::isShipped($app)) {
314
			return false;
315
		}
316
317
		return OC_Installer::removeApp($app);
318
	}
319
320
	/**
321
	 * This function set an app as disabled in appconfig.
322
	 * @param string $app app
323
	 */
324
	public static function disable($app) {
325
		if($app === 'files') {
326
			throw new \Exception("files can't be disabled.");
327
		}
328
		self::$enabledAppsCache = array(); // flush
329
		// check if app is a shipped app or not. if not delete
330
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
331
332
		// Convert OCS ID to regular application identifier
333
		if(self::getInternalAppIdByOcs($app) !== false) {
334
			$app = self::getInternalAppIdByOcs($app);
335
		}
336
337
		OC_Appconfig::setValue($app, 'enabled', 'no' );
338
	}
339
340
	/**
341
	 * adds an entry to the navigation
342
	 * @param array $data array containing the data
343
	 * @return bool
344
	 *
345
	 * This function adds a new entry to the navigation visible to users. $data
346
	 * is an associative array.
347
	 * The following keys are required:
348
	 *   - id: unique id for this entry ('addressbook_index')
349
	 *   - href: link to the page
350
	 *   - name: Human readable name ('Addressbook')
351
	 *
352
	 * The following keys are optional:
353
	 *   - icon: path to the icon of the app
354
	 *   - order: integer, that influences the position of your application in
355
	 *     the navigation. Lower values come first.
356
	 */
357
	public static function addNavigationEntry($data) {
358
		OC::$server->getNavigationManager()->add($data);
359
		return true;
360
	}
361
362
	/**
363
	 * marks a navigation entry as active
364
	 * @param string $id id of the entry
365
	 * @return bool
366
	 *
367
	 * This function sets a navigation entry as active and removes the 'active'
368
	 * property from all other entries. The templates can use this for
369
	 * highlighting the current position of the user.
370
	 */
371
	public static function setActiveNavigationEntry($id) {
372
		OC::$server->getNavigationManager()->setActiveEntry($id);
373
		return true;
374
	}
375
376
	/**
377
	 * Get the navigation entries for the $app
378
	 * @param string $app app
379
	 * @return array an array of the $data added with addNavigationEntry
380
	 *
381
	 * Warning: destroys the existing entries
382
	 */
383
	public static function getAppNavigationEntries($app) {
384
		if (is_file(self::getAppPath($app) . '/appinfo/app.php')) {
385
			OC::$server->getNavigationManager()->clear();
386
			require $app . '/appinfo/app.php';
387
			return OC::$server->getNavigationManager()->getAll();
388
		}
389
		return array();
390
	}
391
392
	/**
393
	 * gets the active Menu entry
394
	 * @return string id or empty string
395
	 *
396
	 * This function returns the id of the active navigation entry (set by
397
	 * setActiveNavigationEntry
398
	 */
399
	public static function getActiveNavigationEntry() {
400
		return OC::$server->getNavigationManager()->getActiveEntry();
401
	}
402
403
	/**
404
	 * Returns the Settings Navigation
405
	 * @return string
406
	 *
407
	 * This function returns an array containing all settings pages added. The
408
	 * entries are sorted by the key 'order' ascending.
409
	 */
410
	public static function getSettingsNavigation() {
411
		$l = \OC::$server->getL10N('lib');
412
413
		$settings = array();
414
		// by default, settings only contain the help menu
415
		if (OC_Util::getEditionString() === '' &&
416
			OC_Config::getValue('knowledgebaseenabled', true) == true
417
		) {
418
			$settings = array(
419
				array(
420
					"id" => "help",
421
					"order" => 1000,
422
					"href" => OC_Helper::linkToRoute("settings_help"),
423
					"name" => $l->t("Help"),
424
					"icon" => OC_Helper::imagePath("settings", "help.svg")
425
				)
426
			);
427
		}
428
429
		// if the user is logged-in
430
		if (OC_User::isLoggedIn()) {
431
			// personal menu
432
			$settings[] = array(
433
				"id" => "personal",
434
				"order" => 1,
435
				"href" => OC_Helper::linkToRoute("settings_personal"),
436
				"name" => $l->t("Personal"),
437
				"icon" => OC_Helper::imagePath("settings", "personal.svg")
438
			);
439
440
			// if there are some settings forms
441 View Code Duplication
			if (!empty(self::$settingsForms)) {
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...
442
				// settings menu
443
				$settings[] = array(
444
					"id" => "settings",
445
					"order" => 1000,
446
					"href" => OC_Helper::linkToRoute("settings_settings"),
447
					"name" => $l->t("Settings"),
448
					"icon" => OC_Helper::imagePath("settings", "settings.svg")
449
				);
450
			}
451
452
			//SubAdmins are also allowed to access user management
453 View Code Duplication
			if (OC_SubAdmin::isSubAdmin(OC_User::getUser())) {
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...
454
				// admin users menu
455
				$settings[] = array(
456
					"id" => "core_users",
457
					"order" => 2,
458
					"href" => OC_Helper::linkToRoute("settings_users"),
459
					"name" => $l->t("Users"),
460
					"icon" => OC_Helper::imagePath("settings", "users.svg")
461
				);
462
			}
463
464
465
			// if the user is an admin
466 View Code Duplication
			if (OC_User::isAdminUser(OC_User::getUser())) {
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...
467
				// admin settings
468
				$settings[] = array(
469
					"id" => "admin",
470
					"order" => 1000,
471
					"href" => OC_Helper::linkToRoute("settings_admin"),
472
					"name" => $l->t("Admin"),
473
					"icon" => OC_Helper::imagePath("settings", "admin.svg")
474
				);
475
			}
476
		}
477
478
		$navigation = self::proceedNavigation($settings);
479
		return $navigation;
480
	}
481
482
	// This is private as well. It simply works, so don't ask for more details
483
	private static function proceedNavigation($list) {
484
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
485
		foreach ($list as &$navEntry) {
486
			if ($navEntry['id'] == $activeApp) {
487
				$navEntry['active'] = true;
488
			} else {
489
				$navEntry['active'] = false;
490
			}
491
		}
492
		unset($navEntry);
493
494
		usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}'));
495
496
		return $list;
497
	}
498
499
	/**
500
	 * Get the path where to install apps
501
	 *
502
	 * @return string
503
	 */
504
	public static function getInstallPath() {
505
		if (OC_Config::getValue('appstoreenabled', true) == false) {
506
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by OC_App::getInstallPath of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
507
		}
508
509
		foreach (OC::$APPSROOTS as $dir) {
510
			if (isset($dir['writable']) && $dir['writable'] === true) {
511
				return $dir['path'];
512
			}
513
		}
514
515
		OC_Log::write('core', 'No application directories are marked as writable.', OC_Log::ERROR);
516
		return null;
517
	}
518
519
520
	/**
521
	 * search for an app in all app-directories
522
	 * @param $appId
523
	 * @return mixed (bool|string)
524
	 */
525
	protected static function findAppInDirectories($appId) {
526
		static $app_dir = array();
527
528
		if (isset($app_dir[$appId])) {
529
			return $app_dir[$appId];
530
		}
531
532
		$possibleApps = array();
533
		foreach(OC::$APPSROOTS as $dir) {
534
			if(file_exists($dir['path'] . '/' . $appId)) {
535
				$possibleApps[] = $dir;
536
			}
537
		}
538
539
		if (empty($possibleApps)) {
540
			return false;
541
		} elseif(count($possibleApps) === 1) {
542
			$dir = array_shift($possibleApps);
543
			$app_dir[$appId] = $dir;
544
			return $dir;
545
		} else {
546
			$versionToLoad = array();
547
			foreach($possibleApps as $possibleApp) {
548
				$version = self::getAppVersionByPath($possibleApp['path']);
549
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
550
					$versionToLoad = array(
551
						'dir' => $possibleApp,
552
						'version' => $version,
553
					);
554
				}
555
			}
556
			$app_dir[$appId] = $versionToLoad['dir'];
557
			return $versionToLoad['dir'];
558
			//TODO - write test
559
		}
560
	}
561
562
	/**
563
	 * Get the directory for the given app.
564
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
565
	 *
566
	 * @param string $appId
567
	 * @return string|false
568
	 */
569
	public static function getAppPath($appId) {
570
		if ($appId === null || trim($appId) === '') {
571
			return false;
572
		}
573
574
		if (($dir = self::findAppInDirectories($appId)) != false) {
575
			return $dir['path'] . '/' . $appId;
576
		}
577
		return false;
578
	}
579
580
581
	/**
582
	 * check if an app's directory is writable
583
	 *
584
	 * @param string $appId
585
	 * @return bool
586
	 */
587
	public static function isAppDirWritable($appId) {
588
		$path = self::getAppPath($appId);
589
		return ($path !== false) ? is_writable($path) : false;
590
	}
591
592
	/**
593
	 * Get the path for the given app on the access
594
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
595
	 *
596
	 * @param string $appId
597
	 * @return string|false
598
	 */
599
	public static function getAppWebPath($appId) {
600
		if (($dir = self::findAppInDirectories($appId)) != false) {
601
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
602
		}
603
		return false;
604
	}
605
606
	/**
607
	 * get the last version of the app, either from appinfo/version or from appinfo/info.xml
608
	 *
609
	 * @param string $appId
610
	 * @return string
611
	 */
612
	public static function getAppVersion($appId) {
613
		$file = self::getAppPath($appId);
614
		return ($file !== false) ? self::getAppVersionByPath($file) : '0';
615
	}
616
617
	/**
618
	 * get app's version based on it's path
619
	 * @param string $path
620
	 * @return string
621
	 */
622
	public static function getAppVersionByPath($path) {
623
		$versionFile = $path . '/appinfo/version';
624
		$infoFile = $path . '/appinfo/info.xml';
625
		if(is_file($versionFile)) {
626
			return trim(file_get_contents($versionFile));
627
		}else{
628
			$appData = self::getAppInfo($infoFile, true);
629
			return isset($appData['version']) ? $appData['version'] : '';
630
		}
631
	}
632
633
634
	/**
635
	 * Read all app metadata from the info.xml file
636
	 *
637
	 * @param string $appId id of the app or the path of the info.xml file
638
	 * @param boolean $path (optional)
639
	 * @return array|null
640
	 * @note all data is read from info.xml, not just pre-defined fields
641
	 */
642
	public static function getAppInfo($appId, $path = false) {
643
		if ($path) {
644
			$file = $appId;
645
		} else {
646
			if (isset(self::$appInfo[$appId])) {
647
				return self::$appInfo[$appId];
648
			}
649
			$file = self::getAppPath($appId) . '/appinfo/info.xml';
650
		}
651
652
		$parser = new \OC\App\InfoParser(\OC::$server->getHTTPHelper(), \OC::$server->getURLGenerator());
653
		$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 661 which is incompatible with the return type documented by OC_App::getAppInfo of type array|null.
Loading history...
654
655
		if(is_array($data)) {
656
			$data = OC_App::parseAppInfo($data);
657
		}
658
659
		self::$appInfo[$appId] = $data;
660
661
		return $data;
662
	}
663
664
	/**
665
	 * Returns the navigation
666
	 * @return array
667
	 *
668
	 * This function returns an array containing all entries added. The
669
	 * entries are sorted by the key 'order' ascending. Additional to the keys
670
	 * given for each app the following keys exist:
671
	 *   - active: boolean, signals if the user is on this navigation entry
672
	 */
673
	public static function getNavigation() {
674
		$entries = OC::$server->getNavigationManager()->getAll();
675
		$navigation = self::proceedNavigation($entries);
676
		return $navigation;
677
	}
678
679
	/**
680
	 * get the id of loaded app
681
	 *
682
	 * @return string
683
	 */
684
	public static function getCurrentApp() {
685
		$script = substr(OC_Request::scriptName(), strlen(OC::$WEBROOT) + 1);
686
		$topFolder = substr($script, 0, strpos($script, '/'));
687
		if (empty($topFolder)) {
688
			$path_info = OC_Request::getPathInfo();
689
			if ($path_info) {
690
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
691
			}
692
		}
693
		if ($topFolder == 'apps') {
694
			$length = strlen($topFolder);
695
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
696
		} else {
697
			return $topFolder;
698
		}
699
	}
700
701
	/**
702
	 * get the forms for either settings, admin or personal
703
	 */
704
	public static function getForms($type) {
705
		$forms = array();
706
		switch ($type) {
707
			case 'settings':
708
				$source = self::$settingsForms;
709
				break;
710
			case 'admin':
711
				$source = self::$adminForms;
712
				break;
713
			case 'personal':
714
				$source = self::$personalForms;
715
				break;
716
			default:
717
				return array();
718
		}
719
		foreach ($source as $form) {
720
			$forms[] = include $form;
721
		}
722
		return $forms;
723
	}
724
725
	/**
726
	 * register a settings form to be shown
727
	 */
728
	public static function registerSettings($app, $page) {
729
		self::$settingsForms[] = $app . '/' . $page . '.php';
730
	}
731
732
	/**
733
	 * register an admin form to be shown
734
	 *
735
	 * @param string $app
736
	 * @param string $page
737
	 */
738
	public static function registerAdmin($app, $page) {
739
		self::$adminForms[] = $app . '/' . $page . '.php';
740
	}
741
742
	/**
743
	 * register a personal form to be shown
744
	 */
745
	public static function registerPersonal($app, $page) {
746
		self::$personalForms[] = $app . '/' . $page . '.php';
747
	}
748
749
	public static function registerLogIn($entry) {
750
		self::$altLogin[] = $entry;
751
	}
752
753
	public static function getAlternativeLogIns() {
754
		return self::$altLogin;
755
	}
756
757
	/**
758
	 * get a list of all apps in the apps folder
759
	 * @return array an array of app names (string IDs)
760
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
761
	 */
762
	public static function getAllApps() {
763
764
		$apps = array();
765
766
		foreach (OC::$APPSROOTS as $apps_dir) {
767
			if (!is_readable($apps_dir['path'])) {
768
				OC_Log::write('core', 'unable to read app folder : ' . $apps_dir['path'], OC_Log::WARN);
769
				continue;
770
			}
771
			$dh = opendir($apps_dir['path']);
772
773
			if (is_resource($dh)) {
774
				while (($file = readdir($dh)) !== false) {
775
776
					if ($file[0] != '.' and is_file($apps_dir['path'] . '/' . $file . '/appinfo/app.php')) {
777
778
						$apps[] = $file;
779
780
					}
781
782
				}
783
			}
784
785
		}
786
787
		return $apps;
788
	}
789
790
	/**
791
	 * Lists all apps, this is used in apps.php
792
	 * @return array
793
	 */
794
	public static function listAllApps($onlyLocal = false) {
795
		$installedApps = OC_App::getAllApps();
796
797
		//TODO which apps do we want to blacklist and how do we integrate
798
		// blacklisting with the multi apps folder feature?
799
800
		$blacklist = array('files'); //we don't want to show configuration for these
801
		$appList = array();
802
		$l = \OC::$server->getL10N('core');
803
804
		foreach ($installedApps as $app) {
805
			if (array_search($app, $blacklist) === false) {
806
807
				$info = OC_App::getAppInfo($app);
808
809
				if (!isset($info['name'])) {
810
					OC_Log::write('core', 'App id "' . $app . '" has no name in appinfo', OC_Log::ERROR);
811
					continue;
812
				}
813
814
				$enabled = OC_Appconfig::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(isset($info['shipped']) and ($info['shipped'] == 'true')) {
828
					$info['internal'] = true;
829
					$info['internallabel'] = (string)$l->t('Recommended');
830
					$info['internalclass'] = 'recommendedapp';
831
					$info['removable'] = false;
832
				} else {
833
					$info['internal'] = false;
834
					$info['removable'] = true;
835
				}
836
837
				$info['update'] = OC_Installer::isUpdateAvailable($app);
838
839
				$appIcon = self::getAppPath($app) . '/img/' . $app.'.svg';
840
				if (file_exists($appIcon)) {
841
					$info['preview'] = OC_Helper::imagePath($app, $app.'.svg');
842
					$info['previewAsIcon'] = true;
843
				} else {
844
					$appIcon = self::getAppPath($app) . '/img/app.svg';
845
					if (file_exists($appIcon)) {
846
						$info['preview'] = OC_Helper::imagePath($app, 'app.svg');
847
						$info['previewAsIcon'] = true;
848
					}
849
				}
850
				$info['version'] = OC_App::getAppVersion($app);
851
				$appList[] = $info;
852
			}
853
		}
854
		if ($onlyLocal) {
855
			$remoteApps = array();
856
		} else {
857
			$remoteApps = OC_App::getAppstoreApps();
858
		}
859
		if ($remoteApps) {
860
			// Remove duplicates
861
			foreach ($appList as $app) {
862
				foreach ($remoteApps AS $key => $remote) {
863
					if ($app['name'] === $remote['name'] ||
864
						(isset($app['ocsid']) &&
865
						$app['ocsid'] ===  $remote['id'])) {
866
						unset($remoteApps[$key]);
867
					}
868
				}
869
			}
870
			$combinedApps = array_merge($appList, $remoteApps);
871
		} else {
872
			$combinedApps = $appList;
873
		}
874
		// bring the apps into the right order with a custom sort function
875
		usort($combinedApps, function ($a, $b) {
876
877
			// priority 1: active
878
			if ($a['active'] != $b['active']) {
879
				return $b['active'] - $a['active'];
880
			}
881
882
			// priority 2: shipped
883
			$aShipped = (array_key_exists('shipped', $a) && $a['shipped'] === 'true') ? 1 : 0;
884
			$bShipped = (array_key_exists('shipped', $b) && $b['shipped'] === 'true') ? 1 : 0;
885
			if ($aShipped !== $bShipped) {
886
				return ($bShipped - $aShipped);
887
			}
888
889
			// priority 3: recommended
890
			$internalClassA = isset($a['internalclass']) ? $a['internalclass'] : '';
891
			$internalClassB = isset($b['internalclass']) ? $b['internalclass'] : '';
892
			if ($internalClassA != $internalClassB) {
893
				$aTemp = ($internalClassA == 'recommendedapp' ? 1 : 0);
894
				$bTemp = ($internalClassB == 'recommendedapp' ? 1 : 0);
895
				return ($bTemp - $aTemp);
896
			}
897
898
			// priority 4: alphabetical
899
			return strcasecmp($a['name'], $b['name']);
900
901
		});
902
903
		return $combinedApps;
904
	}
905
906
	/**
907
	 * Returns the internal app ID or false
908
	 * @param string $ocsID
909
	 * @return string|false
910
	 */
911
	protected static function getInternalAppIdByOcs($ocsID) {
912
		if(is_numeric($ocsID)) {
913
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
914
			if(array_search($ocsID, $idArray)) {
915
				return array_search($ocsID, $idArray);
916
			}
917
		}
918
		return false;
919
	}
920
921
	/**
922
	 * get a list of all apps on apps.owncloud.com
923
	 * @return array, multi-dimensional array of apps.
0 ignored issues
show
Documentation introduced by
The doc-type array, could not be parsed: Expected "|" or "end of type", but got "," at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
924
	 *     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
925
	 */
926
	public static function getAppstoreApps($filter = 'approved', $category = null) {
927
		$categories = array($category);
928
		if (is_null($category)) {
929
			$categoryNames = OC_OCSClient::getCategories(\OC_Util::getVersion());
930
			if (is_array($categoryNames)) {
931
				// Check that categories of apps were retrieved correctly
932
				if (!$categories = array_keys($categoryNames)) {
933
					return false;
934
				}
935
			} else {
936
				return false;
937
			}
938
		}
939
940
		$page = 0;
941
		$remoteApps = OC_OCSClient::getApplications($categories, $page, $filter, \OC_Util::getVersion());
942
		$app1 = array();
943
		$i = 0;
944
		$l = \OC::$server->getL10N('core');
945
		foreach ($remoteApps as $app) {
0 ignored issues
show
Bug introduced by
The expression $remoteApps of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
946
			$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
947
			// enhance app info (for example the description)
948
			$app1[$i] = OC_App::parseAppInfo($app);
949
			$app1[$i]['author'] = $app['personid'];
950
			$app1[$i]['ocs_id'] = $app['id'];
951
			$app1[$i]['internal'] = 0;
952
			$app1[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
953
			$app1[$i]['update'] = false;
954
			$app1[$i]['groups'] = false;
955
			$app1[$i]['score'] = $app['score'];
956
			$app1[$i]['removable'] = false;
957
			if ($app['label'] == 'recommended') {
958
				$app1[$i]['internallabel'] = (string)$l->t('Recommended');
959
				$app1[$i]['internalclass'] = 'recommendedapp';
960
			}
961
962
			$i++;
963
		}
964
965
		if (empty($app1)) {
966
			return false;
967
		} else {
968
			return $app1;
969
		}
970
	}
971
972
	public static function shouldUpgrade($app) {
973
		$versions = self::getAppVersions();
974
		$currentVersion = OC_App::getAppVersion($app);
975
		if ($currentVersion && isset($versions[$app])) {
976
			$installedVersion = $versions[$app];
977
			if (version_compare($currentVersion, $installedVersion, '>')) {
978
				return true;
979
			}
980
		}
981
		return false;
982
	}
983
984
	/**
985
	 * Adjust the number of version parts of $version1 to match
986
	 * the number of version parts of $version2.
987
	 *
988
	 * @param string $version1 version to adjust
989
	 * @param string $version2 version to take the number of parts from
990
	 * @return string shortened $version1
991
	 */
992
	private static function adjustVersionParts($version1, $version2) {
993
		$version1 = explode('.', $version1);
994
		$version2 = explode('.', $version2);
995
		// reduce $version1 to match the number of parts in $version2
996
		while (count($version1) > count($version2)) {
997
			array_pop($version1);
998
		}
999
		// if $version1 does not have enough parts, add some
1000
		while (count($version1) < count($version2)) {
1001
			$version1[] = '0';
1002
		}
1003
		return implode('.', $version1);
1004
	}
1005
1006
	/**
1007
	 * Check whether the current ownCloud version matches the given
1008
	 * application's version requirements.
1009
	 *
1010
	 * The comparison is made based on the number of parts that the
1011
	 * app info version has. For example for ownCloud 6.0.3 if the
1012
	 * app info version is expecting version 6.0, the comparison is
1013
	 * made on the first two parts of the ownCloud version.
1014
	 * This means that it's possible to specify "requiremin" => 6
1015
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
1016
	 *
1017
	 * @param string $ocVersion ownCloud version to check against
1018
	 * @param array  $appInfo app info (from xml)
1019
	 *
1020
	 * @return boolean true if compatible, otherwise false
1021
	 */
1022
	public static function isAppCompatible($ocVersion, $appInfo){
1023
		$requireMin = '';
1024
		$requireMax = '';
1025
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
1026
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1027
		} else if (isset($appInfo['requiremin'])) {
1028
			$requireMin = $appInfo['requiremin'];
1029
		} else if (isset($appInfo['require'])) {
1030
			$requireMin = $appInfo['require'];
1031
		}
1032
1033
		if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
1034
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1035
		} else if (isset($appInfo['requiremax'])) {
1036
			$requireMax = $appInfo['requiremax'];
1037
		}
1038
1039
		if (is_array($ocVersion)) {
1040
			$ocVersion = implode('.', $ocVersion);
1041
		}
1042
1043 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...
1044
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1045
		) {
1046
1047
			return false;
1048
		}
1049
1050 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...
1051
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1052
		) {
1053
1054
			return false;
1055
		}
1056
1057
		return true;
1058
	}
1059
1060
	/**
1061
	 * get the installed version of all apps
1062
	 */
1063
	public static function getAppVersions() {
1064
		static $versions;
1065
		if (isset($versions)) { // simple cache, needs to be fixed
1066
			return $versions; // when function is used besides in checkUpgrade
1067
		}
1068
		$versions = array();
1069
		try {
1070
			$query = OC_DB::prepare('SELECT `appid`, `configvalue` FROM `*PREFIX*appconfig`'
1071
				. ' WHERE `configkey` = \'installed_version\'');
1072
			$result = $query->execute();
1073
			while ($row = $result->fetchRow()) {
1074
				$versions[$row['appid']] = $row['configvalue'];
1075
			}
1076
			return $versions;
1077
		} catch (\Exception $e) {
1078
			return array();
1079
		}
1080
	}
1081
1082
	/**
1083
	 * @param mixed $app
1084
	 * @return bool
1085
	 * @throws Exception if app is not compatible with this version of ownCloud
1086
	 * @throws Exception if no app-name was specified
1087
	 */
1088
	public static function installApp($app) {
1089
		$l = \OC::$server->getL10N('core');
1090
		$config = \OC::$server->getConfig();
1091
		$appData=OC_OCSClient::getApplication($app, \OC_Util::getVersion());
1092
1093
		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
1094
		if(!is_numeric($app)) {
1095
			$shippedVersion=self::getAppVersion($app);
1096
			if($appData && version_compare($shippedVersion, $appData['version'], '<')) {
1097
				$app = self::downloadApp($app);
1098
			} else {
1099
				$app = OC_Installer::installShippedApp($app);
1100
			}
1101
		} else {
1102
			// Maybe the app is already installed - compare the version in this
1103
			// case and use the local already installed one.
1104
			// FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me.
1105
			$internalAppId = self::getInternalAppIdByOcs($app);
1106
			if($internalAppId !== false) {
1107
				if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) {
1108
					$app = self::downloadApp($app);
1109
				} else {
1110
					self::enable($internalAppId);
1111
					$app = $internalAppId;
1112
				}
1113
			} else {
1114
				$app = self::downloadApp($app);
1115
			}
1116
		}
1117
1118
		if($app!==false) {
1119
			// check if the app is compatible with this version of ownCloud
1120
			$info = self::getAppInfo($app);
1121
			$version=OC_Util::getVersion();
1122
			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...
Bug introduced by
It seems like $info defined by self::getAppInfo($app) on line 1120 can also be of type null; however, OC_App::isAppCompatible() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1123
				throw new \Exception(
1124
					$l->t('App \"%s\" can\'t be installed because it is not compatible with this version of ownCloud.',
1125
						array($info['name'])
1126
					)
1127
				);
1128
			}
1129
1130
			// check for required dependencies
1131
			$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1132
			$missing = $dependencyAnalyzer->analyze($info);
0 ignored issues
show
Bug introduced by
It seems like $info defined by self::getAppInfo($app) on line 1120 can also be of type null; however, OC\App\DependencyAnalyzer::analyze() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1133
			if(!empty($missing)) {
1134
				$missingMsg = join(PHP_EOL, $missing);
1135
				throw new \Exception(
1136
					$l->t('App \"%s\" cannot be installed because the following dependencies are not fulfilled: %s',
1137
						array($info['name'], $missingMsg)
1138
					)
1139
				);
1140
			}
1141
1142
			$config->setAppValue($app, 'enabled', 'yes');
1143
			if(isset($appData['id'])) {
1144
				$config->setAppValue($app, 'ocsid', $appData['id'] );
1145
			}
1146
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1147
		}else{
1148
			throw new \Exception($l->t("No app name specified"));
1149
		}
1150
1151
		return $app;
1152
	}
1153
1154
	/**
1155
	 * update the database for the app and call the update script
1156
	 *
1157
	 * @param string $appId
1158
	 * @return bool
1159
	 */
1160
	public static function updateApp($appId) {
1161
		if (file_exists(self::getAppPath($appId) . '/appinfo/database.xml')) {
1162
			OC_DB::updateDbFromStructure(self::getAppPath($appId) . '/appinfo/database.xml');
1163
		}
1164
		if (!self::isEnabled($appId)) {
1165
			return false;
1166
		}
1167
		if (file_exists(self::getAppPath($appId) . '/appinfo/update.php')) {
1168
			self::loadApp($appId, false);
1169
			include self::getAppPath($appId) . '/appinfo/update.php';
1170
		}
1171
1172
		//set remote/public handlers
1173
		$appData = self::getAppInfo($appId);
1174
		if (array_key_exists('ocsid', $appData)) {
1175
			OC_Appconfig::setValue($appId, 'ocsid', $appData['ocsid']);
1176
		} elseif(OC_Appconfig::getValue($appId, 'ocsid', null) !== null) {
1177
			OC_Appconfig::deleteKey($appId, 'ocsid');
1178
		}
1179 View Code Duplication
		foreach ($appData['remote'] 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...
1180
			OCP\CONFIG::setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1181
		}
1182 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...
1183
			OCP\CONFIG::setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1184
		}
1185
1186
		self::setAppTypes($appId);
1187
1188
		$version = \OC_App::getAppVersion($appId);
1189
		\OC_Appconfig::setValue($appId, 'installed_version', $version);
1190
1191
		return true;
1192
	}
1193
1194
	/**
1195
	 * @param string $appId
1196
	 * @return \OC\Files\View
1197
	 */
1198
	public static function getStorage($appId) {
1199
		if (OC_App::isEnabled($appId)) { //sanity check
1200
			if (OC_User::isLoggedIn()) {
1201
				$view = new \OC\Files\View('/' . OC_User::getUser());
1202
				if (!$view->file_exists($appId)) {
1203
					$view->mkdir($appId);
1204
				}
1205
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1206
			} else {
1207
				OC_Log::write('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', OC_Log::ERROR);
1208
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by OC_App::getStorage of type OC\Files\View.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1209
			}
1210
		} else {
1211
			OC_Log::write('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', OC_Log::ERROR);
1212
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by OC_App::getStorage of type OC\Files\View.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1213
		}
1214
	}
1215
1216
	/**
1217
	 * parses the app data array and enhanced the 'description' value
1218
	 *
1219
	 * @param array $data the app data
1220
	 * @return array improved app data
1221
	 */
1222
	public static function parseAppInfo(array $data) {
1223
1224
		// just modify the description if it is available
1225
		// otherwise this will create a $data element with an empty 'description'
1226
		if(isset($data['description'])) {
1227
			// sometimes the description contains line breaks and they are then also
1228
			// shown in this way in the app management which isn't wanted as HTML
1229
			// manages line breaks itself
1230
1231
			// first of all we split on empty lines
1232
			$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1233
1234
			$result = [];
1235
			foreach ($paragraphs as $value) {
1236
				// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1237
				// with a single space - also trims whitespace
1238
				$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1239
			}
1240
1241
			// join the single paragraphs with a empty line in between
1242
			$data['description'] = implode("\n\n", $result);
1243
1244
		}
1245
1246
		return $data;
1247
	}
1248
}
1249