Completed
Push — master ( 8a505e...14bc9b )
by Morris
51:24 queued 31:58
created

NavigationManager   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 288
Duplicated Lines 13.54 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 39
loc 288
rs 7.4757
c 0
b 0
f 0
wmc 53
lcom 1
cbo 8

10 Methods

Rating   Name   Duplication   Size   Complexity  
B add() 0 18 5
A __construct() 0 13 1
A getAll() 0 16 3
C proceedNavigation() 0 25 11
A clear() 0 5 1
A setActiveEntry() 0 3 1
A getActiveEntry() 0 3 1
D init() 32 127 26
A isAdmin() 7 7 2
A isSubadmin() 0 7 2

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 NavigationManager 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 NavigationManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud GmbH
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author John Molakvoæ (skjnldsv) <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OC;
31
32
use OC\App\AppManager;
33
use OC\Group\Manager;
34
use OCP\App\IAppManager;
35
use OCP\IConfig;
36
use OCP\IGroupManager;
37
use OCP\INavigationManager;
38
use OCP\IURLGenerator;
39
use OCP\IUserSession;
40
use OCP\L10N\IFactory;
41
42
/**
43
 * Manages the ownCloud navigation
44
 */
45
46
class NavigationManager implements INavigationManager {
47
	protected $entries = [];
48
	protected $closureEntries = [];
49
	protected $activeEntry;
50
	/** @var bool */
51
	protected $init = false;
52
	/** @var IAppManager|AppManager */
53
	protected $appManager;
54
	/** @var IURLGenerator */
55
	private $urlGenerator;
56
	/** @var IFactory */
57
	private $l10nFac;
58
	/** @var IUserSession */
59
	private $userSession;
60
	/** @var IGroupManager|Manager */
61
	private $groupManager;
62
	/** @var IConfig */
63
	private $config;
64
65
	public function __construct(IAppManager $appManager,
66
						 IURLGenerator $urlGenerator,
67
						 IFactory $l10nFac,
68
						 IUserSession $userSession,
69
						 IGroupManager $groupManager,
70
						 IConfig $config) {
71
		$this->appManager = $appManager;
72
		$this->urlGenerator = $urlGenerator;
73
		$this->l10nFac = $l10nFac;
74
		$this->userSession = $userSession;
75
		$this->groupManager = $groupManager;
76
		$this->config = $config;
77
	}
78
79
	/**
80
	 * Creates a new navigation entry
81
	 *
82
	 * @param array|\Closure $entry Array containing: id, name, order, icon and href key
83
	 *					The use of a closure is preferred, because it will avoid
84
	 * 					loading the routing of your app, unless required.
85
	 * @return void
86
	 */
87
	public function add($entry) {
88
		if ($entry instanceof \Closure) {
89
			$this->closureEntries[] = $entry;
90
			return;
91
		}
92
93
		$entry['active'] = false;
94
		if(!isset($entry['icon'])) {
95
			$entry['icon'] = '';
96
		}
97
		if(!isset($entry['classes'])) {
98
			$entry['classes'] = '';
99
		}
100
		if(!isset($entry['type'])) {
101
			$entry['type'] = 'link';
102
		}
103
		$this->entries[] = $entry;
104
	}
105
106
	/**
107
	 * Get a list of navigation entries
108
	 *
109
	 * @param string $type type of the navigation entries
110
	 * @return array
111
	 */
112
	public function getAll(string $type = 'link'): array {
113
		$this->init();
114
		foreach ($this->closureEntries as $c) {
115
			$this->add($c());
116
		}
117
		$this->closureEntries = array();
118
119
		$result = $this->entries;
120
		if ($type !== 'all') {
121
			$result = array_filter($this->entries, function($entry) use ($type) {
122
				return $entry['type'] === $type;
123
			});
124
		}
125
126
		return $this->proceedNavigation($result);
127
	}
128
129
	/**
130
	 * Sort navigation entries by order, name and set active flag
131
	 *
132
	 * @param array $list
133
	 * @return array
134
	 */
135
	private function proceedNavigation(array $list): array {
136
		usort($list, function($a, $b) {
137
			if (isset($a['order']) && isset($b['order'])) {
138
				return ($a['order'] < $b['order']) ? -1 : 1;
139
			} else if (isset($a['order']) || isset($b['order'])) {
140
				return isset($a['order']) ? -1 : 1;
141
			} else {
142
				return ($a['name'] < $b['name']) ? -1 : 1;
143
			}
144
		});
145
146
		$activeApp = $this->getActiveEntry();
147
		if ($activeApp !== null) {
148
			foreach ($list as $index => &$navEntry) {
149
				if ($navEntry['id'] == $activeApp) {
150
					$navEntry['active'] = true;
151
				} else {
152
					$navEntry['active'] = false;
153
				}
154
			}
155
			unset($navEntry);
156
		}
157
158
		return $list;
159
	}
160
161
162
	/**
163
	 * removes all the entries
164
	 */
165
	public function clear($loadDefaultLinks = true) {
166
		$this->entries = [];
167
		$this->closureEntries = [];
168
		$this->init = !$loadDefaultLinks;
169
	}
170
171
	/**
172
	 * Sets the current navigation entry of the currently running app
173
	 * @param string $id of the app entry to activate (from added $entry)
174
	 */
175
	public function setActiveEntry($id) {
176
		$this->activeEntry = $id;
177
	}
178
179
	/**
180
	 * gets the active Menu entry
181
	 * @return string id or empty string
182
	 *
183
	 * This function returns the id of the active navigation entry (set by
184
	 * setActiveEntry
185
	 */
186
	public function getActiveEntry() {
187
		return $this->activeEntry;
188
	}
189
190
	private function init() {
191
		if ($this->init) {
192
			return;
193
		}
194
		$this->init = true;
195
196
		$l = $this->l10nFac->get('lib');
197 View Code Duplication
		if ($this->config->getSystemValue('knowledgebaseenabled', true)) {
198
			$this->add([
199
				'type' => 'settings',
200
				'id' => 'help',
201
				'order' => 5,
202
				'href' => $this->urlGenerator->linkToRoute('settings_help'),
203
				'name' => $l->t('Help'),
204
				'icon' => $this->urlGenerator->imagePath('settings', 'help.svg'),
205
			]);
206
		}
207
208
		if ($this->userSession->isLoggedIn()) {
209 View Code Duplication
			if ($this->isAdmin()) {
210
				// App management
211
				$this->add([
212
					'type' => 'settings',
213
					'id' => 'core_apps',
214
					'order' => 3,
215
					'href' => $this->urlGenerator->linkToRoute('settings.AppSettings.viewApps'),
216
					'icon' => $this->urlGenerator->imagePath('settings', 'apps.svg'),
217
					'name' => $l->t('Apps'),
218
				]);
219
			}
220
221
			// Personal and (if applicable) admin settings
222
			$this->add([
223
				'type' => 'settings',
224
				'id' => 'settings',
225
				'order' => 1,
226
				'href' => $this->urlGenerator->linkToRoute('settings.PersonalSettings.index'),
227
				'name' => $l->t('Settings'),
228
				'icon' => $this->urlGenerator->imagePath('settings', 'admin.svg'),
229
			]);
230
231
			$logoutUrl = \OC_User::getLogoutUrl($this->urlGenerator);
232
			if($logoutUrl !== '') {
233
				// Logout
234
				$this->add([
235
					'type' => 'settings',
236
					'id' => 'logout',
237
					'order' => 99999,
238
					'href' => $logoutUrl,
239
					'name' => $l->t('Log out'),
240
					'icon' => $this->urlGenerator->imagePath('core', 'actions/logout.svg'),
241
				]);
242
			}
243
244 View Code Duplication
			if ($this->isSubadmin()) {
245
				// User management
246
				$this->add([
247
					'type' => 'settings',
248
					'id' => 'core_users',
249
					'order' => 4,
250
					'href' => $this->urlGenerator->linkToRoute('settings_users'),
251
					'name' => $l->t('Users'),
252
					'icon' => $this->urlGenerator->imagePath('settings', 'users.svg'),
253
				]);
254
			}
255
		}
256
257
		if ($this->appManager === 'null') {
258
			return;
259
		}
260
261
		if ($this->userSession->isLoggedIn()) {
262
			$apps = $this->appManager->getEnabledAppsForUser($this->userSession->getUser());
0 ignored issues
show
Bug introduced by
It seems like $this->userSession->getUser() can be null; however, getEnabledAppsForUser() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
263
		} else {
264
			$apps = $this->appManager->getInstalledApps();
265
		}
266
267
		foreach ($apps as $app) {
268
			if (!$this->userSession->isLoggedIn() && !$this->appManager->isEnabledForUser($app, $this->userSession->getUser())) {
269
				continue;
270
			}
271
272
			// load plugins and collections from info.xml
273
			$info = $this->appManager->getAppInfo($app);
274
			if (empty($info['navigations'])) {
275
				continue;
276
			}
277
			foreach ($info['navigations'] as $nav) {
278
				if (!isset($nav['name'])) {
279
					continue;
280
				}
281
				if (!isset($nav['route'])) {
282
					continue;
283
				}
284
				$role = isset($nav['@attributes']['role']) ? $nav['@attributes']['role'] : 'all';
285
				if ($role === 'admin' && !$this->isAdmin()) {
286
					continue;
287
				}
288
				$l = $this->l10nFac->get($app);
289
				$id = isset($nav['id']) ? $nav['id'] : $app;
290
				$order = isset($nav['order']) ? $nav['order'] : 100;
291
				$type = isset($nav['type']) ? $nav['type'] : 'link';
292
				$route = $this->urlGenerator->linkToRoute($nav['route']);
293
				$icon = isset($nav['icon']) ? $nav['icon'] : 'app.svg';
294
				foreach ([$icon, "$app.svg"] as $i) {
295
					try {
296
						$icon = $this->urlGenerator->imagePath($app, $i);
297
						break;
298
					} catch (\RuntimeException $ex) {
299
						// no icon? - ignore it then
300
					}
301
				}
302
				if ($icon === null) {
303
					$icon = $this->urlGenerator->imagePath('core', 'default-app-icon');
304
				}
305
306
				$this->add([
307
					'id' => $id,
308
					'order' => $order,
309
					'href' => $route,
310
					'icon' => $icon,
311
					'type' => $type,
312
					'name' => $l->t($nav['name']),
313
				]);
314
			}
315
		}
316
	}
317
318 View Code Duplication
	private function isAdmin() {
319
		$user = $this->userSession->getUser();
320
		if ($user !== null) {
321
			return $this->groupManager->isAdmin($user->getUID());
322
		}
323
		return false;
324
	}
325
326
	private function isSubadmin() {
327
		$user = $this->userSession->getUser();
328
		if ($user !== null) {
329
			return $this->groupManager->getSubAdmin()->isSubAdmin($user);
0 ignored issues
show
Bug introduced by
The method getSubAdmin does only exist in OC\Group\Manager, but not in OCP\IGroupManager.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
330
		}
331
		return false;
332
	}
333
}
334