Inspector   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 424
Duplicated Lines 0 %

Test Coverage

Coverage 37.44%

Importance

Changes 0
Metric Value
eloc 194
dl 0
loc 424
rs 3.36
c 0
b 0
f 0
ccs 76
cts 203
cp 0.3744
wmc 63

14 Methods

Rating   Name   Duplication   Size   Complexity  
A describeCallable() 0 2 1
A getViewtypes() 0 2 1
A getPluginHooks() 0 2 1
A getEvents() 0 2 1
A buildHandlerTree() 0 22 5
A getWidgets() 0 9 2
F getViews() 0 72 13
A getWebServices() 0 21 5
A getActions() 0 18 4
A getSimpleCache() 0 22 5
A getServices() 0 14 3
A getViewsData() 0 6 2
C getMenus() 0 82 11
B getRoutes() 0 36 9

How to fix   Complexity   

Complex Class

Complex classes like Inspector 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.

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

1
<?php
2
namespace Elgg\Debug;
3
4
use Elgg\Debug\Inspector\ViewComponent;
5
use Elgg\Includer;
6
use Elgg\Project\Paths;
7
8
/**
9
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
10
 *
11
 * @access private
12
 *
13
 * @package Elgg.Core
14
 * @since   1.11
15
 */
16
class Inspector {
17
18
	/**
19
	 * Get Elgg event information
20
	 *
21
	 * @return array [event,type] => array(handlers)
22
	 */
23 2
	public function getEvents() {
24 2
		return $this->buildHandlerTree(_elgg_services()->events->getAllHandlers());
25
	}
26
27
	/**
28
	 * Get Elgg plugin hooks information
29
	 *
30
	 * @return array [hook,type] => array(handlers)
31
	 */
32 2
	public function getPluginHooks() {
33 2
		return $this->buildHandlerTree(_elgg_services()->hooks->getAllHandlers());
34
	}
35
36
	/**
37
	 * Get all view types for known views
38
	 *
39
	 * @return string[]
40
	 */
41
	public function getViewtypes() {
42
		return array_keys($this->getViewsData()['locations']);
43
	}
44
45
	/**
46
	 * Get Elgg view information
47
	 *
48
	 * @param string $viewtype The Viewtype we wish to inspect
49
	 *
50
	 * @return array [view] => map of priority to ViewComponent[]
51
	 */
52 2
	public function getViews($viewtype = 'default') {
53 2
		$view_data = $this->getViewsData();
54
55
		// maps view name to array of ViewComponent[] with priority as keys
56 2
		$views = [];
57
58
		// add plugins and handle overrides
59 2
		foreach ($view_data['locations'][$viewtype] as $view => $location) {
60 2
			$component = new ViewComponent();
61 2
			$component->view = $view;
62 2
			$component->file = $location;
63
64 2
			$views[$view] = [500 => $component];
65
		}
66
67
		// now extensions
68 2
		foreach ($view_data['extensions'] as $view => $extensions) {
69 2
			$view_list = [];
70 2
			foreach ($extensions as $priority => $ext_view) {
71 2
				if (isset($views[$ext_view])) {
72 2
					$view_list[$priority] = $views[$ext_view][500];
73
				}
74
			}
75 2
			if (count($view_list) > 0) {
76 2
				$views[$view] = $view_list;
77
			}
78
		}
79
80 2
		ksort($views);
81
82
		// now overrides
83 2
		foreach ($views as $view => $view_list) {
84 2
			if (!empty($view_data['overrides'][$viewtype][$view])) {
85 2
				$overrides_list = [];
86 2
				foreach ($view_data['overrides'][$viewtype][$view] as $i => $location) {
87 2
					$component = new ViewComponent();
88 2
					$component->overridden = true;
89 2
					$component->view = $view;
90 2
					$component->file = $location;
91
92 2
					$overrides_list["o:$i"] = $component;
93
				}
94 2
				$views[$view] = $overrides_list + $view_list;
95
			}
96
		}
97
98
		// view handlers
99 2
		$handlers = _elgg_services()->hooks->getAllHandlers();
100
101 2
		$input_filtered_views = [];
102 2
		if (!empty($handlers['view_vars'])) {
103 2
			$input_filtered_views = array_keys($handlers['view_vars']);
104
		}
105
106 2
		$filtered_views = [];
107 2
		if (!empty($handlers['view'])) {
108
			$filtered_views = array_keys($handlers['view']);
109
		}
110
111 2
		$global_hooks = [];
112 2
		if (!empty($handlers['view_vars']['all'])) {
113
			$global_hooks[] = 'view_vars, all';
114
		}
115 2
		if (!empty($handlers['view']['all'])) {
116
			$global_hooks[] = 'view, all';
117
		}
118
119
		return [
120 2
			'views' => $views,
121 2
			'global_hooks' => $global_hooks,
122 2
			'input_filtered_views' => $input_filtered_views,
123 2
			'filtered_views' => $filtered_views,
124
		];
125
	}
126
127
	/**
128
	 * Get Elgg widget information
129
	 *
130
	 * @return array [widget] => array(name, contexts)
131
	 */
132
	public function getWidgets() {
133
		$tree = [];
134
		foreach (_elgg_services()->widgets->getAllTypes() as $handler => $handler_obj) {
135
			$tree[$handler] = [$handler_obj->name, implode(',', array_values($handler_obj->context))];
136
		}
137
138
		ksort($tree);
139
140
		return $tree;
141
	}
142
143
144
	/**
145
	 * Get Elgg actions information
146
	 *
147
	 * returns [action] => array(file, access)
148
	 *
149
	 * @return array
150
	 */
151 2
	public function getActions() {
152 2
		$tree = [];
153
		$access = [
154 2
			'public' => 'public',
155
			'logged_in' => 'logged in only',
156
			'admin' => 'admin only',
157
		];
158 2
		$start = strlen(elgg_get_root_path());
159 2
		foreach (_elgg_services()->actions->getAllActions() as $action => $info) {
160 2
			if (isset($info['file'])) {
161 2
				$info['file'] = substr($info['file'], $start);
162 2
			} else if ($info['controller']) {
163 2
				$info['file'] = $this->describeCallable($info['controller']);
164
			}
165 2
			$tree[$action] = [$info['file'], $access[$info['access']]];
166
		}
167 2
		ksort($tree);
168 2
		return $tree;
169
	}
170
171
	/**
172
	 * Get simplecache information
173
	 *
174
	 * @return array [views]
175
	 */
176
	public function getSimpleCache() {
177
		
178
		$simplecache = elgg_extract('simplecache', $this->getViewsData(), []);
179
		$locations = elgg_extract('locations', $this->getViewsData(), []);
180
		
181
		$tree = [];
182
		foreach ($simplecache as $view => $foo) {
183
			$tree[$view] = '';
184
		}
185
		
186
		// add all static views
187
		foreach ($locations as $viewtype) {
188
			foreach ($viewtype as $view => $location) {
189
				if (pathinfo($location, PATHINFO_EXTENSION) !== 'php') {
190
					$tree[$view] = '';
191
				}
192
			}
193
		}
194
195
		ksort($tree);
196
197
		return $tree;
198
	}
199
200
	/**
201
	 * Get Elgg route information
202
	 *
203
	 * returns [route] => array(path, resource)
204
	 *
205
	 * @return array
206
	 */
207
	public function getRoutes() {
208
		$tree = [];
209
		foreach (_elgg_services()->routeCollection->all() as $name => $route) {
210
			$handler = $route->getDefault('_handler') ? : '';
211
			if ($handler) {
212
				$handler = $this->describeCallable($handler);
213
			}
214
215
			$controller = $route->getDefault('_controller') ? : '';
216
			if ($controller) {
217
				$controller = $this->describeCallable($controller);
218
			}
219
220
			$resource = $route->getDefault('_resource') ? : '';
221
222
			$file = $route->getDefault('_file') ? : '';
223
224
			$middleware = $route->getDefault('_middleware') ? : '';
225
			$middleware = array_map(function($e) {
226
				return $this->describeCallable($e);
227
			}, $middleware);
0 ignored issues
show
Bug introduced by Ismayil Khayredinov
It seems like $middleware can also be of type string; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

227
			}, /** @scrutinizer ignore-type */ $middleware);
Loading history...
228
229
			$tree[$name] = [
230
				$route->getPath(),
231
				$resource,
232
				$handler,
233
				$controller,
234
				$file,
235
				$middleware,
236
			];
237
		}
238
		uasort($tree, function($e1, $e2) {
239
			return strcmp($e1[0], $e2[0]);
240
		});
241
242
		return $tree;
243
	}
244
245
	/**
246
	 * Get Elgg web services API methods
247
	 *
248
	 * @return array [method] => array(function, parameters, call_method, api auth, user auth)
249
	 */
250
	public function getWebServices() {
251
		global $API_METHODS;
252
253
		$tree = [];
254
		foreach ($API_METHODS as $method => $info) {
255
			$params = implode(', ', array_keys(elgg_extract('parameters', $info, [])));
256
			if (!$params) {
257
				$params = 'none';
258
			}
259
			$tree[$method] = [
260
				$info['function'],
261
				"params: $params",
262
				$info['call_method'],
263
				($info['require_api_auth']) ? 'API authentication required' : 'No API authentication required',
264
				($info['require_user_auth']) ? 'User authentication required' : 'No user authentication required',
265
			];
266
		}
267
268
		ksort($tree);
269
270
		return $tree;
271
	}
272
273
	/**
274
	 * Get information about registered menus
275
	 *
276
	 * @return array [menu name] => array(item name => array(text, href, section, parent))
277
	 */
278
	public function getMenus() {
279
280
		$menus = _elgg_config()->menus;
281
282
		// get JIT menu items
283
		// note that 'river' is absent from this list - hooks attempt to get object/subject entities cause problems
284
		$jit_menus = ['annotation', 'entity', 'login', 'longtext', 'owner_block', 'user_hover', 'widget'];
285
286
		// create generic ElggEntity, ElggAnnotation, ElggUser, ElggWidget
287
		$annotation = new \ElggAnnotation();
288
		$annotation->id = 999;
289
		$annotation->name = 'generic_comment';
290
		$annotation->value = 'testvalue';
291
292
		$entity = new \ElggObject();
293
		$entity->guid = 999;
0 ignored issues
show
Bug introduced by Steve Clay
The property guid is declared read-only in ElggEntity.
Loading history...
294
		$entity->subtype = 'blog';
295
		$entity->title = 'test entity';
296
		$entity->access_id = ACCESS_PUBLIC;
297
298
		$user = new \ElggUser();
299
		$user->guid = 999;
300
		$user->name = "Test User";
301
		$user->username = 'test_user';
302
303
		$widget = new \ElggWidget();
304
		$widget->guid = 999;
305
		$widget->title = 'test widget';
306
307
		// call plugin hooks
308
		foreach ($jit_menus as $type) {
309
			$params = ['entity' => $entity, 'annotation' => $annotation, 'user' => $user];
310
			switch ($type) {
311
				case 'owner_block':
312
				case 'user_hover':
313
					$params['entity'] = $user;
314
					break;
315
				case 'widget':
316
					// this does not work because you cannot set a guid on an entity
317
					$params['entity'] = $widget;
318
					break;
319
				case 'longtext':
320
					$params['id'] = rand();
321
					break;
322
				default:
323
					break;
324
			}
325
			$menus[$type] = _elgg_services()->hooks->trigger('register', "menu:$type", $params, []);
326
		}
327
328
		// put the menus in tree form for inspection
329
		$tree = [];
330
331
		foreach ($menus as $menu_name => $attributes) {
332
			foreach ($attributes as $item) {
333
				/* @var \ElggMenuItem $item */
334
				$name = $item->getName();
335
				$text = htmlspecialchars($item->getText(), ENT_QUOTES, 'UTF-8', false);
336
				$href = $item->getHref();
337
				if ($href === false) {
338
					$href = 'not a link';
339
				} elseif ($href === "") {
340
					$href = 'not a direct link - possibly ajax';
341
				}
342
				$section = $item->getSection();
343
				$parent = $item->getParentName();
344
				if (!$parent) {
345
					$parent = 'none';
346
				}
347
348
				$tree[$menu_name][$name] = [
349
					"text: $text",
350
					"href: $href",
351
					"section: $section",
352
					"parent: $parent",
353
				];
354
			}
355
		}
356
357
		ksort($tree);
358
359
		return $tree;
360
	}
361
362
	/**
363
	 * Get a string description of a callback
364
	 *
365
	 * E.g. "function_name", "Static::method", "(ClassName)->method", "(Closure path/to/file.php:23)"
366
	 *
367
	 * @param mixed  $callable  Callable
368
	 * @param string $file_root If provided, it will be removed from the beginning of file names
369
	 * @return string
370
	 */
371 2
	public function describeCallable($callable, $file_root = '') {
372 2
		return _elgg_services()->handlers->describeCallable($callable, $file_root);
373
	}
374
375
	/**
376
	 * Build a tree of event handlers
377
	 *
378
	 * @param array $all_handlers Set of handlers from a HooksRegistrationService
379
	 *
380
	 * @return array
381
	 */
382 2
	protected function buildHandlerTree($all_handlers) {
383 2
		$tree = [];
384 2
		$root = elgg_get_root_path();
385 2
		$handlers_svc = _elgg_services()->handlers;
386
387 2
		foreach ($all_handlers as $hook => $types) {
388 2
			foreach ($types as $type => $priorities) {
389 2
				ksort($priorities);
390
391 2
				foreach ($priorities as $priority => $handlers) {
392 2
					foreach ($handlers as $callable) {
393 2
						$description = $handlers_svc->describeCallable($callable, $root);
394 2
						$callable = "$priority: $description";
395 2
						$tree["$hook, $type"][] = $callable;
396
					}
397
				}
398
			}
399
		}
400
401 2
		ksort($tree);
402
403 2
		return $tree;
404
	}
405
406
	/**
407
	 * Get data from the Views service
408
	 *
409
	 * @return array
410
	 */
411 2
	private function getViewsData() {
412 2
		static $data;
413 2
		if ($data === null) {
414 1
			$data = _elgg_services()->views->getInspectorData();
415
		}
416 2
		return $data;
417
	}
418
419
	/**
420
	 * Returns public DI services
421
	 *
422
	 * returns [service_name => [class, path]]
423
	 *
424
	 * @return array
425
	 */
426
	public function getServices() {
427
		$tree = [];
428
429
		foreach (_elgg_services()->dic_loader->getDefinitions() as $definition) {
430
			$services = Includer::includeFile($definition);
431
432
			foreach ($services as $name => $service) {
433
				$tree[$name] = [get_class(elgg()->$name), Paths::sanitize($definition, false)];
434
			}
435
		}
436
437
		ksort($tree);
438
439
		return $tree;
440
	}
441
}
442