Filtered   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 441
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 7

Test Coverage

Coverage 90.91%

Importance

Changes 0
Metric Value
wmc 41
lcom 3
cbo 7
dl 0
loc 441
ccs 150
cts 165
cp 0.9091
rs 9.1199
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getArrayFromValueList() 0 3 1
A getParser() 0 8 2
A setParser() 0 3 1
A getPrintrequests() 0 3 1
A hasTemplates() 0 7 2
A getName() 0 3 1
A handleParameters() 0 19 1
A getParamDefinitions() 0 25 2
A getLinker() 0 3 2
A addConfigToOutput() 0 21 3
A registerResourceModules() 0 4 1
A uniqid() 0 4 2
A getResultsForJs() 0 7 2
A addError() 0 3 1
C getFilterHtml() 0 84 9
B getViewHtml() 0 70 6
A getResultText() 0 53 4

How to fix   Complexity   

Complex Class

Complex classes like Filtered 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 Filtered, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * File holding the SRFFiltered class.
5
 *
6
 * @author Stephan Gambke
7
 *
8
 */
9
10
namespace SRF\Filtered;
11
12
use Exception;
13
use Html;
14
use SMW\Message;
15
use SMW\Query\PrintRequest;
16
use SMW\Query\QueryLinker;
17
use SMW\Query\ResultPrinters\ResultPrinter;
18
use SMWOutputs;
19
use SMWPropertyValue;
20
use SMWQueryResult;
21
22
/**
23
 * Result printer that displays results in switchable views and offers
24
 * client-side (JavaScript based) filtering.
25
 *
26
 * This result printer is ultimately planned to replace exhibit. Currently only
27
 * a list view is available. It is not yet possible to switch between views.
28
 * There is also only the 'value' filter available yet.
29
 *
30
 * Syntax of the #ask call:
31
 * (This is only a syntax example. For currently available features see the
32
 * documentation of the various classes.)
33
 *
34
 * {{#ask:[[SomeCondition]]
35
 * |? SomePrintout |+filter=value, someFutureFilter |+value filter switches=and or, disable, all, none |+someFutureFilter filter option=someOptionValue
36
 * |? SomeOtherPrintout |+filter=value, someOtherFutureFilter |+someOtherFutureFilter filter option=someOptionValue
37
 *
38
 * |format=filtered
39
 * |views=list, someFutureView, someOtherFutureView
40
 *
41
 * |list view type=list
42
 * |list view template=ListItem
43
 *
44
 * |someFutureView view option=someOptionValue
45
 *
46
 * |someOtherFutureView view option=someOptionValue
47
 *
48
 * }}
49
 *
50
 * All format specific parameters are optional, although leaving the 'views'
51
 * parameter empty probably does not make much sense.
52
 *
53
 */
54
class Filtered extends ResultPrinter {
55
56
	/**
57
	 * The available view types
58
	 *
59
	 * @var array of Strings
60
	 */
61
	private $mViewTypes = [
62
		'list' => 'ListView',
63
		'calendar' => 'CalendarView',
64
		'table' => 'TableView',
65
		'map' => 'MapView',
66
	];
67
68
	/**
69
	 * The available filter types
70
	 *
71
	 * @var array of Strings
72
	 */
73
	private $mFilterTypes = [
74
		'value' => 'ValueFilter',
75
		'distance' => 'DistanceFilter',
76
		'number' => 'NumberFilter',
77
	];
78
79
	private $viewNames;
80
	private $parameters;
81
	private $filtersOnTop;
82
	private $printrequests;
83
84
	private $parser;
85
86
	/**
87
	 * @param string $valueList
88
	 * @param string $delimiter
89
	 *
90
	 * @return string[]
91
	 */
92 1
	public function getArrayFromValueList( $valueList, $delimiter = ',' ) {
93 1
		return array_map( 'trim', explode( $delimiter, $valueList ) );
94
	}
95
96
	/**
97
	 * @return \Parser | \StubObject | null
98
	 */
99 2
	public function getParser() {
100
101 2
		if ( $this->parser === null ) {
102 2
			$this->setParser( $GLOBALS['wgParser'] );
103
		}
104
105 2
		return $this->parser;
106
	}
107
108
	/**
109
	 * @param \Parser | \StubObject $parser
110
	 */
111 2
	public function setParser( $parser ) {
112 2
		$this->parser = $parser;
113 2
	}
114
115
	/**
116
	 * @return mixed
117
	 */
118
	public function getPrintrequests() {
119
		return $this->printrequests;
120
	}
121
122
	public function hasTemplates( $hasTemplates = null ) {
123
		$ret = $this->hasTemplates;
124
		if ( is_bool( $hasTemplates ) ) {
125
			$this->hasTemplates = $hasTemplates;
126
		}
127
		return $ret;
128
	}
129
130
	/**
131
	 * Get a human readable label for this printer.
132
	 *
133
	 * @return string
134
	 */
135
	public function getName() {
136
		return wfMessage( 'srf-printername-filtered' )->text();
137
	}
138
139
	/**
140
	 * Does any additional parameter handling that needs to be done before the
141
	 * actual result is build.
142
	 *
143
	 * @param array $params
144
	 * @param $outputMode
145
	 */
146 2
	protected function handleParameters( array $params, $outputMode ) {
147 2
		parent::handleParameters( $params, $outputMode );
148
149
		// // Set in SMWResultPrinter:
150
		// $this->mIntro = $params['intro'];
151
		// $this->mOutro = $params['outro'];
152
		// $this->mSearchlabel = $params['searchlabel'] === false ? null : $params['searchlabel'];
153
		// $this->mLinkFirst = true | false;
154
		// $this->mLinkOthers = true | false;
155
		// $this->mDefault = str_replace( '_', ' ', $params['default'] );
156
		// $this->mShowHeaders = SMW_HEADERS_HIDE | SMW_HEADERS_PLAIN | SMW_HEADERS_SHOW;
157
158 2
		$this->mSearchlabel = null;
159
160 2
		$this->parameters = $params;
161 2
		$this->viewNames = explode( ',', $params['views'] );
162 2
		$this->filtersOnTop = $params['filter position'] === 'top';
163
164 2
	}
165
166
	/**
167
	 * Return serialised results in specified format.
168
	 *
169
	 * @param SMWQueryResult $res
170
	 * @param $outputmode
171
	 *
172
	 * @return string
173
	 */
174 2
	protected function getResultText( SMWQueryResult $res, $outputmode ) {
175
176
		// collect the query results in an array
177
		/** @var ResultItem[] $resultItems */
178 2
		$resultItems = [];
179 2
		while ( $row = $res->getNext() ) {
180 2
			$resultItems[$this->uniqid()] = new ResultItem( $row, $this );
181 2
			usleep( 1 ); // This is ugly, but for now th opnly way to get all resultItems. See #288.
182
		}
183
184
		$config = [
185 2
			'query' => $res->getQueryString(),
186
			'printrequests' => [],
187
			'views' => [],
188
			'data' => [],
189
		];
190
191 2
		list( $filterHtml, $printrequests ) = $this->getFilterHtml( $res, $resultItems );
192
193 2
		$this->printrequests = $printrequests;
194 2
		$config['printrequests'] = $printrequests;
195
196 2
		list( $viewHtml, $config ) = $this->getViewHtml( $res, $resultItems, $config );
197
198 2
		SMWOutputs::requireResource( 'ext.srf.filtered' );
199
200 2
		$id = $this->uniqid();
201
		// wrap all in a div
202 2
		$html = '<div class="filtered-spinner"><div class="smw-overlay-spinner"></div></div>';
203 2
		$html .= $this->filtersOnTop ? $filterHtml . $viewHtml : $viewHtml . $filterHtml;
204 2
		$html = Html::rawElement( 'div', [ 'class' => 'filtered ' . $id, 'id' => $id ], $html );
205
206 2
		$config['data'] = $this->getResultsForJs( $resultItems );
207
208 2
		$config['filtersOnTop'] = $this->filtersOnTop;
209 2
		$this->addConfigToOutput( $id, $config );
210
211
		try {
212 2
			$this->fullParams['limit']->getOriginalValue();
213
		}
214
		catch ( Exception $exception ) {
215
			$res->getQuery()->setLimit( 0 );
216
		}
217
218 2
		$link = QueryLinker::get( $res->getQuery() );
219 2
		$link->setCaption( Message::get( "srf-filtered-noscript-link-caption" ) );
220 2
		$link->setParameter( 'table', 'format' );
221
222 2
		SMWOutputs::requireResource( 'ext.srf.filtered' );
223 2
		$this->registerResources( [], [ 'ext.srf.filtered' ] );
224
225 2
		return $html;
226
	}
227
228
	/**
229
	 * @see SMWResultPrinter::getParamDefinitions
230
	 * @see DefaultConfig.php of param-processor/param-processor for allowed types
231
	 *
232
	 * @since 1.8
233
	 *
234
	 * @param $definitions array of IParamDefinition
235
	 *
236
	 * @return array of IParamDefinition|array
237
	 */
238 2
	public function getParamDefinitions( array $definitions ) {
239 2
		$params = parent::getParamDefinitions( $definitions );
240
241 2
		$params[] = [
242
			// 'type' => 'string',
243
			'name' => 'views',
244
			'message' => 'srf-paramdesc-filtered-views',
245
			'default' => '',
246
			// 'islist' => false,
247
		];
248
249 2
		$params[] = [
250
			// 'type' => 'string',
251
			'name' => 'filter position',
252
			'message' => 'srf-paramdesc-filtered-filter-position',
253
			'default' => 'top',
254
			// 'islist' => false,
255
		];
256
257 2
		foreach ( $this->mViewTypes as $viewType ) {
258 2
			$params = array_merge( $params, call_user_func( [ 'SRF\Filtered\View\\' . $viewType, 'getParameters' ] ) );
259
		}
260
261 2
		return $params;
262
	}
263
264 2
	public function getLinker( $firstcol = false, $force = false ) {
265 2
		return ( $force ) ? $this->mLinker : parent::getLinker( $firstcol );
266
	}
267
268 2
	private function addConfigToOutput( $id, $config ) {
269
270 2
		if ( $this->getParser()->getOutput() !== null ) {
271 2
			$getter = [ $this->getParser()->getOutput(), 'getExtensionData' ];
272 2
			$setter = [ $this->getParser()->getOutput(), 'setExtensionData' ];
273
		} else {
274
			$getter = [ \RequestContext::getMain()->getOutput(), 'getProperty' ];
275
			$setter = [ \RequestContext::getMain()->getOutput(), 'setProperty' ];
276
		}
277
278 2
		$previousConfig = call_user_func( $getter, 'srf-filtered-config' );
279
280 2
		if ( $previousConfig === null ) {
281 2
			$previousConfig = [];
282
		}
283
284 2
		$previousConfig[$id] = $config;
285
286 2
		call_user_func( $setter, 'srf-filtered-config', $previousConfig );
287
288 2
	}
289
290
	/**
291
	 * @param string | string[] | null $resourceModules
292
	 */
293 1
	protected function registerResourceModules( $resourceModules ) {
294
295 1
		array_map( 'SMWOutputs::requireResource', (array)$resourceModules );
296 1
	}
297
298
	/**
299
	 * @param string|null $id
300
	 *
301
	 * @return string
302
	 */
303 2
	public function uniqid( $id = null ) {
304 2
		$hashedId = ( $id === null ) ? uniqid() : md5( $id );
305 2
		return base_convert( $hashedId, 16, 36 );
306
	}
307
308
	/**
309
	 * @param ResultItem[] $result
310
	 *
311
	 * @return array
312
	 */
313 2
	protected function getResultsForJs( $result ) {
314 2
		$resultAsArray = [];
315 2
		foreach ( $result as $id => $row ) {
316 2
			$resultAsArray[$id] = $row->getArrayRepresentation();
317
		}
318 2
		return $resultAsArray;
319
	}
320
321 1
	public function addError( $errorMessage ) {
322 1
		parent::addError( $errorMessage );
323 1
	}
324
325
	/**
326
	 * @param SMWQueryResult $res
327
	 * @param $result
328
	 *
329
	 * @return array
330
	 */
331 2
	protected function getFilterHtml( SMWQueryResult $res, $result ) {
332
333
		// prepare filter data for inclusion in HTML and  JS
334 2
		$filterHtml = '';
335
336 2
		$printrequests = [];
337
338
		/** @var PrintRequest $printRequest */
339 2
		foreach ( $res->getPrintRequests() as $printRequest ) {
340
341
			$prConfig = [
342 2
				'mode' => $printRequest->getMode(),
343 2
				'label' => $printRequest->getLabel(),
344 2
				'outputformat' => $printRequest->getOutputFormat(),
345 2
				'type' => $printRequest->getTypeID(),
346
			];
347
348 2
			if ( $printRequest->getData() instanceof SMWPropertyValue ) {
0 ignored issues
show
Bug introduced by Stephan Gambke
The class SMWPropertyValue does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
349 2
				$prConfig['property'] = $printRequest->getData()->getInceptiveProperty()->getKey();
350
			}
351
352 2
			if ( filter_var( $printRequest->getParameter( 'hide' ), FILTER_VALIDATE_BOOLEAN ) ) {
353
				$prConfig['hide'] = true;
354
			}
355
356 2
			$filtersParam = $printRequest->getParameter( 'filter' );
357
358 2
			if ( $filtersParam ) {
359
360 1
				$filtersForPrintout = $this->getArrayFromValueList( $filtersParam );
361
362 1
				foreach ( $filtersForPrintout as $filterName ) {
363
364 1
					if ( array_key_exists( $filterName, $this->mFilterTypes ) ) {
365
366
						/** @var \SRF\Filtered\Filter\Filter $filter */
367 1
						$filterClassName = '\SRF\Filtered\Filter\\' . $this->mFilterTypes[$filterName];
368 1
						$filter = new  $filterClassName( $result, $printRequest, $this );
369
370 1
						if ( $filter->isValidFilterForPropertyType() ) {
371
372 1
							$this->registerResourceModules( $filter->getResourceModules() );
373
374 1
							$filterid = $this->uniqid();
375 1
							$filterHtml .= Html::rawElement(
376 1
								'div',
377 1
								[ 'id' => $filterid, 'class' => "filtered-filter filtered-$filterName" ],
378 1
								$filter->getResultText()
379
							);
380
381 1
							$filterdata = $filter->getJsConfig();
382 1
							$filterdata['type'] = $filterName;
383 1
							$filterdata['label'] = $printRequest->getLabel();
384
385 1
							$prConfig['filters'][$filterid] = $filterdata;
386
387 1
							foreach ( $result as $row ) {
388 1
								$row->setData( $filterid, $filter->getJsDataForRow( $row ) );
389
							}
390
						} else {
391
							// TODO: I18N
392 1
							$this->addError(
393 1
								"The '$filterName' filter can not be used on the '{$printRequest->getLabel()}' printout."
394
							);
395
						}
396
397
					}
398
				}
399
			}
400
401 2
			$printrequests[$this->uniqid( $printRequest->getHash() )] = $prConfig;
402
		}
403
404 2
		$filterHtml .= '<div class="filtered-filter-spinner" style="display: none;"><div class="smw-overlay-spinner"></div></div>';
405
406
		// wrap filters in a div
407 2
		$filterHtml = Html::rawElement(
408 2
			'div',
409 2
			[ 'class' => 'filtered-filters', 'style' => 'display:none' ],
410 2
			$filterHtml
411
		);
412
413 2
		return [ $filterHtml, $printrequests ];
414
	}
415
416
	/**
417
	 * @param SMWQueryResult $res
418
	 * @param $resultItems
419
	 * @param $config
420
	 *
421
	 * @return array
422
	 */
423 2
	protected function getViewHtml( SMWQueryResult $res, $resultItems, $config ) {
424
425
		// prepare view data for inclusion in HTML and  JS
426 2
		$viewHtml = '';
427 2
		$viewSelectorsHtml = '';
428
429 2
		foreach ( $this->viewNames as $viewName ) {
430
431
			// cut off the selector label (if one was specified) from the actual view name
432 2
			$viewnameComponents = explode( '=', $viewName, 2 );
433
434 2
			$viewName = trim( $viewnameComponents[0] );
435
436 2
			if ( array_key_exists( $viewName, $this->mViewTypes ) ) {
437
438
				// generate unique id
439 2
				$viewid = $this->uniqid();
440
441 2
				if ( count( $viewnameComponents ) > 1 ) {
442
					// a selector label was specified in the wiki text
443
					$viewSelectorLabel = trim( $viewnameComponents[1] );
444
				} else {
445
					// use the default selector label
446 2
					$viewSelectorLabel = Message::get( 'srf-filtered-selectorlabel-' . $viewName );
447
				}
448
449
				/** @var \SRF\Filtered\View\View $view */
450 2
				$viewClassName = '\SRF\Filtered\View\\' . $this->mViewTypes[$viewName];
451 2
				$view = new $viewClassName( $resultItems, $this->parameters, $this, $viewSelectorLabel );
452
453 2
				$initErrorMsg = $view->getInitError();
454
455 2
				if ( $initErrorMsg !== null ) {
456 1
					$res->addErrors( [ $this->msg( $initErrorMsg )->text() ] );
0 ignored issues
show
Bug introduced by Stephan Gambke
The method text() does not seem to exist on object<SMW\Message>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
457
				} else {
458
459 1
					$this->registerResourceModules( $view->getResourceModules() );
460
461 1
					$viewHtml .= Html::rawElement(
462 1
						'div',
463 1
						[ 'id' => $viewid, 'class' => "filtered-view filtered-$viewName $viewid" ],
464 1
						$view->getResultText()
465
					);
466 1
					$viewSelectorsHtml .= Html::rawElement(
467 1
						'div',
468 1
						[ 'class' => "filtered-view-selector filtered-$viewName $viewid" ],
469 1
						$viewSelectorLabel
470
					);
471
472 1
					foreach ( $resultItems as $row ) {
473 1
						$row->setData( $viewid, $view->getJsDataForRow( $row ) );
474
					}
475
476 1
					$config['views'][$viewid] = array_merge( [ 'type' => $viewName ], $view->getJsConfig() );
477
				}
478
			}
479
		}
480
481 2
		$viewHtml = Html::rawElement(
482 2
			'div',
483 2
			[ 'class' => 'filtered-views', 'style' => 'display:none' ],
484 2
			Html::rawElement(
485 2
				'div',
486 2
				[ 'class' => 'filtered-views-selectors-container', 'style' => 'display:none' ],
487 2
				$viewSelectorsHtml
488
			) .
489 2
			Html::rawElement( 'div', [ 'class' => 'filtered-views-container' ], $viewHtml )
490
		);
491 2
		return [ $viewHtml, $config ];
492
	}
493
494
}