Inspector   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 428
Duplicated Lines 0 %

Test Coverage

Coverage 38.21%

Importance

Changes 0
Metric Value
eloc 197
dl 0
loc 428
ccs 81
cts 212
cp 0.3821
rs 3.44
c 0
b 0
f 0
wmc 62

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getEvents() 0 2 1
A getViewtypes() 0 2 1
A buildHandlerTree() 0 22 5
A getWidgets() 0 9 2
A describeCallable() 0 2 1
A getSimpleCache() 0 21 5
F getViews() 0 75 13
A getActions() 0 21 4
A getServices() 0 27 5
A getViewsData() 0 7 2
C getMenus() 0 87 12
B getRoutes() 0 45 10
A getSeeders() 0 2 1

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
use Elgg\Menu\MenuItems;
8
9
/**
10
 * Debug inspector
11
 *
12
 * @internal
13
 * @since 1.11
14
 */
15
class Inspector {
16
17
	/**
18
	 * Get Elgg event information
19
	 *
20
	 * @return array [event,type] => array(handlers)
21
	 */
22 1
	public function getEvents() {
23 1
		return $this->buildHandlerTree(_elgg_services()->events->getAllHandlers());
24
	}
25
26
	/**
27
	 * Get all view types for known views
28
	 *
29
	 * @return string[]
30
	 */
31
	public function getViewtypes() {
32
		return array_keys($this->getViewsData()['locations']);
33
	}
34
35
	/**
36
	 * Get Elgg view information
37
	 *
38
	 * @param string $viewtype The Viewtype we wish to inspect
39
	 *
40
	 * @return array [view] => map of priority to ViewComponent[]
41
	 */
42 1
	public function getViews($viewtype = 'default') {
43 1
		$view_data = $this->getViewsData();
44
45
		// maps view name to array of ViewComponent[] with priority as keys
46 1
		$views = [];
47
48
		// add plugins and handle overrides
49 1
		foreach ($view_data['locations'][$viewtype] as $view => $location) {
50 1
			$component = new ViewComponent();
51 1
			$component->view = $view;
52 1
			$component->file = $location;
53
54 1
			$views[$view] = [500 => $component];
55
		}
56
57
		// now extensions
58 1
		foreach ($view_data['extensions'] as $view => $extensions) {
59 1
			$view_list = [];
60 1
			foreach ($extensions as $priority => $ext_view) {
61 1
				if (isset($views[$ext_view])) {
62 1
					$view_list[$priority] = $views[$ext_view][500];
63
				}
64
			}
65
			
66 1
			if (count($view_list) > 0) {
67 1
				$views[$view] = $view_list;
68
			}
69
		}
70
71 1
		ksort($views);
72
73
		// now overrides
74 1
		foreach ($views as $view => $view_list) {
75 1
			if (!empty($view_data['overrides'][$viewtype][$view])) {
76 1
				$overrides_list = [];
77 1
				foreach ($view_data['overrides'][$viewtype][$view] as $i => $location) {
78 1
					$component = new ViewComponent();
79 1
					$component->overridden = true;
80 1
					$component->view = $view;
81 1
					$component->file = $location;
82
83 1
					$overrides_list["o:$i"] = $component;
84
				}
85
				
86 1
				$views[$view] = $overrides_list + $view_list;
87
			}
88
		}
89
90
		// view handlers
91 1
		$handlers = _elgg_services()->events->getAllHandlers();
92
93 1
		$input_filtered_views = [];
94 1
		if (!empty($handlers['view_vars'])) {
95 1
			$input_filtered_views = array_keys($handlers['view_vars']);
96
		}
97
98 1
		$filtered_views = [];
99 1
		if (!empty($handlers['view'])) {
100
			$filtered_views = array_keys($handlers['view']);
101
		}
102
103 1
		$global_events = [];
104 1
		if (!empty($handlers['view_vars']['all'])) {
105
			$global_events[] = 'view_vars, all';
106
		}
107
		
108 1
		if (!empty($handlers['view']['all'])) {
109
			$global_events[] = 'view, all';
110
		}
111
112 1
		return [
113 1
			'views' => $views,
114 1
			'global_events' => $global_events,
115 1
			'input_filtered_views' => $input_filtered_views,
116 1
			'filtered_views' => $filtered_views,
117 1
		];
118
	}
119
120
	/**
121
	 * Get Elgg widget information
122
	 *
123
	 * @return array [widget] => array(name, contexts)
124
	 */
125
	public function getWidgets() {
126
		$tree = [];
127
		foreach (_elgg_services()->widgets->getAllTypes() as $handler => $handler_obj) {
128
			$tree[$handler] = [$handler_obj->name, implode(',', array_values($handler_obj->context))];
129
		}
130
131
		ksort($tree);
132
133
		return $tree;
134
	}
135
136
	/**
137
	 * Get Elgg actions information
138
	 *
139
	 * returns [action] => array(file, access)
140
	 *
141
	 * @return array
142
	 */
143 1
	public function getActions() {
144 1
		$tree = [];
145 1
		$access = [
146 1
			'public' => 'public',
147 1
			'logged_in' => 'logged in only',
148 1
			'logged_out' => 'logged out only',
149 1
			'admin' => 'admin only',
150 1
		];
151 1
		$start = strlen(elgg_get_root_path());
152 1
		foreach (_elgg_services()->actions->getAllActions() as $action => $info) {
153 1
			if (isset($info['file'])) {
154 1
				$info['file'] = substr($info['file'], $start);
155 1
			} else if ($info['controller']) {
156 1
				$info['file'] = $this->describeCallable($info['controller']);
157
			}
158
			
159 1
			$tree[$action] = [$info['file'], $access[$info['access']]];
160
		}
161
		
162 1
		ksort($tree);
163 1
		return $tree;
164
	}
165
166
	/**
167
	 * Get simplecache information
168
	 *
169
	 * @return array [views]
170
	 */
171
	public function getSimpleCache() {
172
		$simplecache = elgg_extract('simplecache', $this->getViewsData(), []);
173
		$locations = elgg_extract('locations', $this->getViewsData(), []);
174
		
175
		$tree = [];
176
		foreach ($simplecache as $view => $foo) {
177
			$tree[$view] = '';
178
		}
179
		
180
		// add all static views
181
		foreach ($locations as $viewtype) {
182
			foreach ($viewtype as $view => $location) {
183
				if (pathinfo($location, PATHINFO_EXTENSION) !== 'php') {
184
					$tree[$view] = '';
185
				}
186
			}
187
		}
188
189
		ksort($tree);
190
191
		return $tree;
192
	}
193
194
	/**
195
	 * Get Elgg route information
196
	 *
197
	 * returns [route] => array(path, resource)
198
	 *
199
	 * @return array
200
	 */
201
	public function getRoutes() {
202
		$tree = [];
203
		foreach (_elgg_services()->routeCollection->all() as $name => $route) {
204
			$handler = $route->getDefault('_handler') ?: '';
205
			if ($handler) {
206
				$handler = $this->describeCallable($handler);
207
			}
208
209
			$controller = $route->getDefault('_controller') ?: '';
210
			if ($controller) {
211
				$controller = $this->describeCallable($controller);
212
			}
213
214
			$resource = $route->getDefault('_resource') ?: '';
215
216
			$file = $route->getDefault('_file') ?: '';
217
218
			$middleware = $route->getDefault('_middleware');
219
			if (!is_array($middleware)) {
220
				if (!empty($middleware)) {
221
					$middleware = [$middleware];
222
				} else {
223
					$middleware = [];
224
				}
225
			}
226
			
227
			$middleware = array_map(function($e) {
228
				return $this->describeCallable($e);
229
			}, $middleware);
230
231
			$tree[$name] = [
232
				$route->getPath(),
233
				$resource,
234
				$handler,
235
				$controller,
236
				$file,
237
				$middleware,
238
			];
239
		}
240
		
241
		uasort($tree, function($e1, $e2) {
242
			return strcmp($e1[0], $e2[0]);
243
		});
244
245
		return $tree;
246
	}
247
248
	/**
249
	 * Get information about registered menus
250
	 *
251
	 * @return array [menu name] => array(item name => array(text, href, section, parent))
252
	 */
253
	public function getMenus() {
254
		$menus = _elgg_services()->menus->getAllMenus();
255
256
		// get JIT menu items
257
		// note that 'river' is absent from this list - events attempt to get object/subject entities cause problems
258
		$jit_menus = ['annotation', 'entity', 'login', 'longtext', 'owner_block', 'user_hover', 'widget'];
259
260
		// create generic ElggEntity, ElggAnnotation, ElggUser, ElggWidget
261
		$annotation = new \ElggAnnotation();
262
		$annotation->id = 999;
263
		$annotation->name = 'generic_comment';
264
		$annotation->value = 'testvalue';
265
		$annotation->entity_guid = elgg_get_logged_in_user_guid();
266
267
		$entity = new \ElggObject();
268
		$entity->guid = 999;
269
		$entity->setSubtype('blog');
270
		$entity->title = 'test entity';
271
		$entity->access_id = ACCESS_PUBLIC;
272
273
		$user = elgg_get_logged_in_user_entity();
274
		if (!$user instanceof \ElggUser) {
275
			$user = new \ElggUser();
276
			$user->guid = 999;
277
			$user->name = 'Test User';
278
			$user->username = 'test_user';
279
		}
280
281
		$widget = new \ElggWidget();
282
		$widget->guid = 999;
283
		$widget->title = 'test widget';
284
285
		// call events
286
		foreach ($jit_menus as $type) {
287
			$params = ['entity' => $entity, 'annotation' => $annotation, 'user' => $user];
288
			switch ($type) {
289
				case 'owner_block':
290
				case 'user_hover':
291
					$params['entity'] = $user;
292
					break;
293
				case 'widget':
294
					// this does not work because you cannot set a guid on an entity
295
					$params['entity'] = $widget;
296
					break;
297
				case 'longtext':
298
					$params['id'] = rand();
299
					break;
300
				default:
301
					break;
302
			}
303
			
304
			$menus[$type] = _elgg_services()->events->triggerResults('register', "menu:{$type}", $params, new MenuItems());
305
		}
306
307
		// put the menus in tree form for inspection
308
		$tree = [];
309
310
		foreach ($menus as $menu_name => $attributes) {
311
			/* @var \ElggMenuItem $item */
312
			foreach ($attributes as $item) {
313
				$name = $item->getName();
314
				$text = htmlspecialchars($item->getText() ?? '', ENT_QUOTES, 'UTF-8', false);
315
				$href = $item->getHref();
316
				if ($href === false) {
317
					$href = 'not a link';
318
				} elseif ($href === '') {
319
					$href = 'not a direct link - possibly ajax';
320
				}
321
				
322
				$section = $item->getSection();
323
				$parent = $item->getParentName();
324
				if (!$parent) {
325
					$parent = 'none';
326
				}
327
328
				$tree[$menu_name][$name] = [
329
					"text: {$text}",
330
					"href: {$href}",
331
					"section: {$section}",
332
					"parent: {$parent}",
333
				];
334
			}
335
		}
336
337
		ksort($tree);
338
339
		return $tree;
340
	}
341
342
	/**
343
	 * Get a string description of a callback
344
	 *
345
	 * E.g. "function_name", "Static::method", "(ClassName)->method", "(Closure path/to/file.php:23)"
346
	 *
347
	 * @param mixed  $callable  Callable
348
	 * @param string $file_root If provided, it will be removed from the beginning of file names
349
	 * @return string
350
	 */
351 1
	public function describeCallable($callable, $file_root = '') {
352 1
		return _elgg_services()->handlers->describeCallable($callable, $file_root);
353
	}
354
355
	/**
356
	 * Build a tree of event handlers
357
	 *
358
	 * @param array $all_handlers Set of handlers from a EventsService
359
	 *
360
	 * @return array
361
	 */
362 1
	protected function buildHandlerTree($all_handlers) {
363 1
		$tree = [];
364 1
		$root = elgg_get_root_path();
365 1
		$handlers_svc = _elgg_services()->handlers;
366
367 1
		foreach ($all_handlers as $event => $types) {
368 1
			foreach ($types as $type => $priorities) {
369 1
				ksort($priorities);
370
371 1
				foreach ($priorities as $priority => $handlers) {
372 1
					foreach ($handlers as $callable) {
373 1
						$description = $handlers_svc->describeCallable($callable, $root);
374 1
						$callable = "{$priority}: {$description}";
375 1
						$tree["{$event}, {$type}"][] = $callable;
376
					}
377
				}
378
			}
379
		}
380
381 1
		ksort($tree);
382
383 1
		return $tree;
384
	}
385
386
	/**
387
	 * Get data from the Views service
388
	 *
389
	 * @return array
390
	 */
391 1
	private function getViewsData() {
392 1
		static $data;
393 1
		if ($data === null) {
394 1
			$data = _elgg_services()->views->getInspectorData();
395
		}
396
		
397 1
		return $data;
398
	}
399
400
	/**
401
	 * Returns public DI services
402
	 *
403
	 * returns [service_name => [class, path]]
404
	 *
405
	 * @return array
406
	 */
407
	public function getServices() {
408
		$sources = [
409
			\Elgg\Project\Paths::elgg() . 'engine/public_services.php',
410
		];
411
412
		$plugins = _elgg_services()->plugins->find('active');
413
		foreach ($plugins as $plugin) {
414
			$plugin->autoload(); // make sure all classes are loaded
415
			$sources[] = $plugin->getPath() . \ElggPlugin::PUBLIC_SERVICES_FILENAME;
416
		}
417
418
		$tree = [];
419
		foreach ($sources as $source) {
420
			if (!is_file($source)) {
421
				continue;
422
			}
423
			
424
			$services = Includer::includeFile($source);
425
426
			foreach ($services as $name => $service) {
427
				$tree[$name] = [get_class(elgg()->$name), Paths::sanitize($source, false)];
428
			}
429
		}
430
431
		ksort($tree);
432
433
		return $tree;
434
	}
435
	
436
	/**
437
	 * Get the registered database CLI seeders
438
	 *
439
	 * @return string[]
440
	 */
441
	public function getSeeders(): array {
442
		return _elgg_services()->seeder->getSeederClasses();
443
	}
444
}
445