Completed
Push — master ( ac15b4...98e0ae )
by Jeroen De
38:18 queued 18:19
created

Filtered::getFilterHtml()   C

Complexity

Conditions 9
Paths 9

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 9.0017

Importance

Changes 0
Metric Value
dl 0
loc 84
ccs 35
cts 36
cp 0.9722
rs 6.7935
c 0
b 0
f 0
cc 9
nc 9
nop 2
crap 9.0017

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 1
	 * @param string $delimiter
89 1
	 *
90
	 * @return string[]
91
	 */
92
	public function getArrayFromValueList( $valueList, $delimiter = ',' ) {
93
		return array_map( 'trim', explode( $delimiter, $valueList ) );
94
	}
95 2
96
	/**
97 2
	 * @return \Parser | \StubObject | null
98 2
	 */
99
	public function getParser() {
0 ignored issues
show
Coding Style introduced by
getParser uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
100
101 2
		if ( $this->parser === null ) {
102
			$this->setParser( $GLOBALS['wgParser'] );
103
		}
104
105
		return $this->parser;
106
	}
107 2
108 2
	/**
109 2
	 * @param \Parser | \StubObject $parser
110
	 */
111
	public function setParser( $parser ) {
112
		$this->parser = $parser;
113
	}
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 2
	 *
143 2
	 * @param array $params
144
	 * @param $outputMode
145
	 */
146
	protected function handleParameters( array $params, $outputMode ) {
147
		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 2
		// $this->mLinkOthers = true | false;
155
		// $this->mDefault = str_replace( '_', ' ', $params['default'] );
156 2
		// $this->mShowHeaders = SMW_HEADERS_HIDE | SMW_HEADERS_PLAIN | SMW_HEADERS_SHOW;
157 2
158 2
		$this->mSearchlabel = null;
159
160 2
		$this->parameters = $params;
161
		$this->viewNames = explode( ',', $params['views'] );
162
		$this->filtersOnTop = $params['filter position'] === 'top';
163
164
	}
165
166
	/**
167
	 * Return serialised results in specified format.
168 2
	 *
169
	 * @param SMWQueryResult $res
170
	 * @param $outputmode
171
	 *
172 2
	 * @return string
173 2
	 */
174 2
	protected function getResultText( SMWQueryResult $res, $outputmode ) {
175 2
176
		// collect the query results in an array
177
		/** @var ResultItem[] $resultItems */
178
		$resultItems = [];
179 2
		while ( $row = $res->getNext() ) {
180
			$resultItems[$this->uniqid()] = new ResultItem( $row, $this );
181
			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 2
			'views' => [],
188 2
			'data' => [],
189
		];
190 2
191
		list( $filterHtml, $printrequests ) = $this->getFilterHtml( $res, $resultItems );
192 2
193
		$this->printrequests = $printrequests;
194 2
		$config['printrequests'] = $printrequests;
195
196 2
		list( $viewHtml, $config ) = $this->getViewHtml( $res, $resultItems, $config );
197 2
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
		$html = Html::rawElement( 'div', [ 'class' => 'filtered ' . $id, 'id' => $id ], $html );
205
206 2
		$config['data'] = $this->getResultsForJs( $resultItems );
207 2
208 2
		$config['filtersOnTop'] = $this->filtersOnTop;
209
		$this->addConfigToOutput( $id, $config );
210
211 2
		try {
212 2
			$this->fullParams['limit']->getOriginalValue();
213 2
		}
214
		catch ( Exception $exception ) {
215 2
			$res->getQuery()->setLimit( 0 );
216 2
		}
217
218 2
		$link = QueryLinker::get( $res->getQuery() );
219
		$link->setCaption( Message::get( "srf-filtered-noscript-link-caption" ) );
220
		$link->setParameter( 'table', 'format' );
221
222
		SMWOutputs::requireResource( 'ext.srf.filtered' );
223
		$this->registerResources( [], [ 'ext.srf.filtered' ] );
224
225
		return $html;
226
	}
227
228
	/**
229
	 * @see SMWResultPrinter::getParamDefinitions
230
	 * @see DefaultConfig.php of param-processor/param-processor for allowed types
231 2
	 *
232 2
	 * @since 1.8
233
	 *
234 2
	 * @param $definitions array of IParamDefinition
235
	 *
236
	 * @return array of IParamDefinition|array
237
	 */
238
	public function getParamDefinitions( array $definitions ) {
239
		$params = parent::getParamDefinitions( $definitions );
240
241
		$params[] = [
242 2
			// 'type' => 'string',
243
			'name' => 'views',
244
			'message' => 'srf-paramdesc-filtered-views',
245
			'default' => '',
246
			// 'islist' => false,
247
		];
248
249
		$params[] = [
250 2
			// 'type' => 'string',
251 2
			'name' => 'filter position',
252
			'message' => 'srf-paramdesc-filtered-filter-position',
253
			'default' => 'top',
254 2
			// '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 2
264 2
	public function getLinker( $firstcol = false, $force = false ) {
265 2
		return ( $force ) ? $this->mLinker : parent::getLinker( $firstcol );
266
	}
267
268
	private function addConfigToOutput( $id, $config ) {
269
270
		if ( $this->getParser()->getOutput() !== null ) {
271 2
			$getter = [ $this->getParser()->getOutput(), 'getExtensionData' ];
272
			$setter = [ $this->getParser()->getOutput(), 'setExtensionData' ];
273 2
		} else {
274 2
			$getter = [ \RequestContext::getMain()->getOutput(), 'getProperty' ];
275
			$setter = [ \RequestContext::getMain()->getOutput(), 'setProperty' ];
276
		}
277 2
278
		$previousConfig = call_user_func( $getter, 'srf-filtered-config' );
279 2
280
		if ( $previousConfig === null ) {
281 2
			$previousConfig = [];
282
		}
283
284
		$previousConfig[$id] = $config;
285
286 1
		call_user_func( $setter, 'srf-filtered-config', $previousConfig );
287
288 1
	}
289 1
290
	/**
291
	 * @param string | string[] | null $resourceModules
292
	 */
293
	protected function registerResourceModules( $resourceModules ) {
294
295 2
		array_map( 'SMWOutputs::requireResource', (array)$resourceModules );
296 2
	}
297 2
298
	/**
299
	 * @param string|null $id
300
	 *
301
	 * @return string
302
	 */
303
	public function uniqid( $id = null ) {
304 2
		$hashedId = ( $id === null ) ? uniqid() : md5( $id );
305 2
		return base_convert( $hashedId, 16, 36 );
306 2
	}
307 2
308
	/**
309 2
	 * @param ResultItem[] $result
310
	 *
311
	 * @return array
312 1
	 */
313 1
	protected function getResultsForJs( $result ) {
314 1
		$resultAsArray = [];
315
		foreach ( $result as $id => $row ) {
316
			$resultAsArray[$id] = $row->getArrayRepresentation();
317
		}
318
		return $resultAsArray;
319
	}
320
321 2
	public function addError( $errorMessage ) {
322
		parent::addError( $errorMessage );
323
	}
324 2
325
	/**
326 2
	 * @param SMWQueryResult $res
327
	 * @param $result
328
	 *
329 2
	 * @return array
330
	 */
331
	protected function getFilterHtml( SMWQueryResult $res, $result ) {
332 2
333 2
		// prepare filter data for inclusion in HTML and  JS
334 2
		$filterHtml = '';
335 2
336
		$printrequests = [];
337
338 2
		/** @var PrintRequest $printRequest */
339 2
		foreach ( $res->getPrintRequests() as $printRequest ) {
340
341
			$prConfig = [
342 2
				'mode' => $printRequest->getMode(),
343
				'label' => $printRequest->getLabel(),
344
				'outputformat' => $printRequest->getOutputFormat(),
345
				'type' => $printRequest->getTypeID(),
346 2
			];
347
348 2
			if ( $printRequest->getData() instanceof SMWPropertyValue ) {
0 ignored issues
show
Bug introduced by
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
				$prConfig['property'] = $printRequest->getData()->getInceptiveProperty()->getKey();
350 1
			}
351
352 1
			if ( filter_var( $printRequest->getParameter( 'hide' ), FILTER_VALIDATE_BOOLEAN ) ) {
353
				$prConfig['hide'] = true;
354 1
			}
355
356
			$filtersParam = $printRequest->getParameter( 'filter' );
357 1
358 1
			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 1
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 1
370
						if ( $filter->isValidFilterForPropertyType() ) {
371 1
372
							$this->registerResourceModules( $filter->getResourceModules() );
373 1
374 1
							$filterid = $this->uniqid();
375
							$filterHtml .= Html::rawElement(
376
								'div',
377
								[ 'id' => $filterid, 'class' => "filtered-filter filtered-$filterName" ],
378 1
								$filter->getResultText()
379
							);
380
381
							$filterdata = $filter->getJsConfig();
382
							$filterdata['type'] = $filterName;
383
							$filterdata['label'] = $printRequest->getLabel();
384
385 2
							$prConfig['filters'][$filterid] = $filterdata;
386
387
							foreach ( $result as $row ) {
388 2
								$row->setData( $filterid, $filter->getJsDataForRow( $row ) );
389
							}
390
						} else {
391 2
							// TODO: I18N
392
							$this->addError(
393 2
								"The '$filterName' filter can not be used on the '{$printRequest->getLabel()}' printout."
394
							);
395
						}
396
397
					}
398
				}
399
			}
400
401
			$printrequests[$this->uniqid( $printRequest->getHash() )] = $prConfig;
0 ignored issues
show
Bug introduced by
It seems like $printRequest->getHash() targeting SMW\Query\PrintRequest::getHash() can also be of type boolean; however, SRF\Filtered\Filtered::uniqid() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
402 2
		}
403
404
		$filterHtml .= '<div class="filtered-filter-spinner" style="display: none;"><div class="smw-overlay-spinner"></div></div>';
405 2
406 2
		// wrap filters in a div
407
		$filterHtml = Html::rawElement(
408 2
			'div',
409
			[ 'class' => 'filtered-filters', 'style' => 'display:none' ],
410
			$filterHtml
411 2
		);
412
413 2
		return [ $filterHtml, $printrequests ];
414
	}
415 2
416
	/**
417
	 * @param SMWQueryResult $res
418 2
	 * @param $resultItems
419
	 * @param $config
420 2
	 *
421
	 * @return array
422
	 */
423
	protected function getViewHtml( SMWQueryResult $res, $resultItems, $config ) {
424
425 2
		// prepare view data for inclusion in HTML and  JS
426
		$viewHtml = '';
427
		$viewSelectorsHtml = '';
428
429 2
		foreach ( $this->viewNames as $viewName ) {
430 2
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 1
436
			if ( array_key_exists( $viewName, $this->mViewTypes ) ) {
437
438 1
				// generate unique id
439
				$viewid = $this->uniqid();
440 1
441 1
				if ( count( $viewnameComponents ) > 1 ) {
442
					// a selector label was specified in the wiki text
443 1
					$viewSelectorLabel = trim( $viewnameComponents[1] );
444 1
				} else {
445
					// use the default selector label
446
					$viewSelectorLabel = Message::get( 'srf-filtered-selectorlabel-' . $viewName );
447 2
				}
448
449
				/** @var \SRF\Filtered\View\View $view */
450
				$viewClassName = '\SRF\Filtered\View\\' . $this->mViewTypes[$viewName];
451
				$view = new $viewClassName( $resultItems, $this->parameters, $this, $viewSelectorLabel );
452 2
453 2
				$initErrorMsg = $view->getInitError();
454 2
455
				if ( $initErrorMsg !== null ) {
456 2
					$res->addErrors( [ $this->msg( $initErrorMsg )->text() ] );
0 ignored issues
show
Bug introduced by
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
					$this->registerResourceModules( $view->getResourceModules() );
460
461
					$viewHtml .= Html::rawElement(
462
						'div',
463
						[ 'id' => $viewid, 'class' => "filtered-view filtered-$viewName $viewid" ],
464
						$view->getResultText()
465
					);
466
					$viewSelectorsHtml .= Html::rawElement(
467
						'div',
468
						[ 'class' => "filtered-view-selector filtered-$viewName $viewid" ],
469
						$viewSelectorLabel
470
					);
471
472
					foreach ( $resultItems as $row ) {
473
						$row->setData( $viewid, $view->getJsDataForRow( $row ) );
474
					}
475
476
					$config['views'][$viewid] = array_merge( [ 'type' => $viewName ], $view->getJsConfig() );
477
				}
478
			}
479
		}
480
481
		$viewHtml = Html::rawElement(
482
			'div',
483
			[ 'class' => 'filtered-views', 'style' => 'display:none' ],
484
			Html::rawElement(
485
				'div',
486
				[ 'class' => 'filtered-views-selectors-container', 'style' => 'display:none' ],
487
				$viewSelectorsHtml
488
			) .
489
			Html::rawElement( 'div', [ 'class' => 'filtered-views-container' ], $viewHtml )
490
		);
491
		return [ $viewHtml, $config ];
492
	}
493
494
}