Completed
Push — master ( 9b2ed5...981814 )
by Jeroen De
76:07
created

formats/filtered/src/Filtered.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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() {
0 ignored issues
show
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 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 2
		catch ( Exception $exception ) {
215 2
			$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
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;
0 ignored issues
show
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
		}
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
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 2
					$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
}