Completed
Push — master ( 706a8d...e5a1f4 )
by Stephan
06:19
created

Filtered::getResultText()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 50
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 4

Importance

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