Completed
Push — master ( 5a0c59...249923 )
by Jeroen De
115:19 queued 95:17
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
 * @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
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 = '<div class="filtered-spinner"><div class="smw-overlay-spinner"></div></div>';
197 2
		$html .= $this->filtersOnTop ? $filterHtml . $viewHtml : $viewHtml . $filterHtml;
198 2
		$html = Html::rawElement( 'div', [ 'class' => 'filtered ' . $id, 'id' => $id ], $html );
199
200 2
		$config[ 'data' ] = $this->getResultsForJs( $resultItems );
201
202 2
		$config[ 'filtersOnTop' ] = $this->filtersOnTop;
203 2
		$this->addConfigToOutput( $id, $config );
204
205
		try {
206 2
			$this->fullParams[ 'limit' ]->getOriginalValue();
207 2
		} catch ( Exception $exception ) {
208 2
			$res->getQuery()->setLimit( 0 );
209
		}
210
211 2
		$link = QueryLinker::get( $res->getQuery() );
212 2
		$link->setCaption( Message::get( "srf-filtered-noscript-link-caption" ) );
213 2
		$link->setParameter( 'table', 'format' );
214
215 2
		SMWOutputs::requireResource( 'ext.srf.filtered' );
216 2
		$this->registerResources( [], [ 'ext.srf.filtered' ] );
217
218 2
		return $html;
219
	}
220
221
	/**
222
	 * @see SMWResultPrinter::getParamDefinitions
223
	 * @see DefaultConfig.php of param-processor/param-processor for allowed types
224
	 *
225
	 * @since 1.8
226
	 *
227
	 * @param $definitions array of IParamDefinition
228
	 *
229
	 * @return array of IParamDefinition|array
230
	 */
231 2
	public function getParamDefinitions( array $definitions ) {
232 2
		$params = parent::getParamDefinitions( $definitions );
233
234 2
		$params[] = [
235
			// 'type' => 'string',
236
			'name'    => 'views',
237
			'message' => 'srf-paramdesc-filtered-views',
238
			'default' => '',
239
			// 'islist' => false,
240
		];
241
242 2
		$params[] = [
243
			// 'type' => 'string',
244
			'name'    => 'filter position',
245
			'message' => 'srf-paramdesc-filtered-filter-position',
246
			'default' => 'top',
247
			// 'islist' => false,
248
		];
249
250 2
		foreach ( $this->mViewTypes as $viewType ) {
251 2
			$params = array_merge( $params, call_user_func( [ 'SRF\Filtered\View\\' . $viewType, 'getParameters' ] ) );
252
		}
253
254 2
		return $params;
255
	}
256
257 2
	public function getLinker( $firstcol = false, $force = false ) {
258 2
		return ( $force ) ? $this->mLinker : parent::getLinker( $firstcol );
259
	}
260
261 2
	private function addConfigToOutput( $id, $config ) {
262
263 2
		if ( $this->getParser()->getOutput() !== null ) {
264 2
			$getter = [ $this->getParser()->getOutput(), 'getExtensionData' ];
265 2
			$setter = [ $this->getParser()->getOutput(), 'setExtensionData' ];
266
		} else {
267
			$getter = [ \RequestContext::getMain()->getOutput(), 'getProperty' ];
268
			$setter = [ \RequestContext::getMain()->getOutput(), 'setProperty' ];
269
		}
270
271 2
		$previousConfig = call_user_func( $getter, 'srf-filtered-config' );
272
273 2
		if ( $previousConfig === null ) {
274 2
			$previousConfig = [];
275
		}
276
277 2
		$previousConfig[ $id ] = $config;
278
279 2
		call_user_func( $setter,'srf-filtered-config', $previousConfig );
280
281 2
	}
282
283
	/**
284
	 * @param string | string[] | null $resourceModules
285
	 */
286 1
	protected function registerResourceModules( $resourceModules ) {
287
288 1
		array_map( 'SMWOutputs::requireResource', (array) $resourceModules );
289 1
	}
290
291
	/**
292
	 * @param string|null $id
293
	 * @return string
294
	 */
295 2
	public function uniqid( $id = null ) {
296 2
		$hashedId = ( $id === null ) ? uniqid() : md5( $id );
297 2
		return base_convert( $hashedId, 16, 36 );
298
	}
299
300
	/**
301
	 * @param ResultItem[] $result
302
	 * @return array
303
	 */
304 2
	protected function getResultsForJs( $result ) {
305 2
		$resultAsArray = [];
306 2
		foreach ( $result as $id => $row ) {
307 2
			$resultAsArray[ $id ] = $row->getArrayRepresentation();
308
		}
309 2
		return $resultAsArray;
310
	}
311
312 1
	public function addError( $errorMessage ) {
313 1
		parent::addError( $errorMessage );
314 1
	}
315
316
	/**
317
	 * @param SMWQueryResult $res
318
	 * @param $result
319
	 * @return array
320
	 */
321 2
	protected function getFilterHtml( SMWQueryResult $res, $result ) {
322
323
		// prepare filter data for inclusion in HTML and  JS
324 2
		$filterHtml = '';
325
326 2
		$printrequests = [];
327
328
		/** @var PrintRequest $printRequest */
329 2
		foreach ( $res->getPrintRequests() as $printRequest ) {
330
331
			$prConfig = [
332 2
				'mode' => $printRequest->getMode(),
333 2
				'label' => $printRequest->getLabel(),
334 2
				'outputformat' => $printRequest->getOutputFormat(),
335 2
				'type' => $printRequest->getTypeID(),
336
			];
337
338 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...
339 2
				$prConfig[ 'property' ] = $printRequest->getData()->getInceptiveProperty()->getKey();
340
			}
341
342 2
			if ( filter_var( $printRequest->getParameter( 'hide' ), FILTER_VALIDATE_BOOLEAN ) ) {
343
				$prConfig[ 'hide' ] = true;
344
			}
345
346 2
			$filtersParam = $printRequest->getParameter( 'filter' );
347
348 2
			if ( $filtersParam ) {
349
350 1
				$filtersForPrintout = $this->getArrayFromValueList( $filtersParam );
351
352 1
				foreach ( $filtersForPrintout as $filterName ) {
353
354 1
					if ( array_key_exists( $filterName, $this->mFilterTypes ) ) {
355
356
						/** @var \SRF\Filtered\Filter\Filter $filter */
357 1
						$filterClassName = '\SRF\Filtered\Filter\\' . $this->mFilterTypes[ $filterName ];
358 1
						$filter = new  $filterClassName( $result, $printRequest, $this );
359
360 1
						if ( $filter->isValidFilterForPropertyType() ) {
361
362 1
							$this->registerResourceModules( $filter->getResourceModules() );
363
364 1
							$filterid = $this->uniqid();
365 1
							$filterHtml .= Html::rawElement( 'div', [ 'id' => $filterid, 'class' => "filtered-filter filtered-$filterName" ], $filter->getResultText() );
366
367 1
							$filterdata = $filter->getJsConfig();
368 1
							$filterdata[ 'type' ] = $filterName;
369 1
							$filterdata[ 'label' ] = $printRequest->getLabel();
370
371 1
							$prConfig[ 'filters' ][ $filterid ] = $filterdata;
372
373 1
							foreach ( $result as $row ) {
374 1
								$row->setData( $filterid, $filter->getJsDataForRow( $row ) );
375
							}
376
						} else {
377
							// TODO: I18N
378 1
							$this->addError( "The '$filterName' filter can not be used on the '{$printRequest->getLabel()}' printout." );
379
						}
380
381
					}
382
				}
383
			}
384
385 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...
386
		}
387
388 2
		$filterHtml .= '<div class="filtered-filter-spinner" style="display: none;"><div class="smw-overlay-spinner"></div></div>';
389
390
		// wrap filters in a div
391 2
		$filterHtml = Html::rawElement( 'div', [ 'class' => 'filtered-filters', 'style' => 'display:none' ], $filterHtml );
392
393 2
		return [ $filterHtml, $printrequests ];
394
	}
395
396
	/**
397
	 * @param SMWQueryResult $res
398
	 * @param $resultItems
399
	 * @param $config
400
	 * @return array
401
	 */
402 2
	protected function getViewHtml( SMWQueryResult $res, $resultItems, $config ) {
403
404
		// prepare view data for inclusion in HTML and  JS
405 2
		$viewHtml = '';
406 2
		$viewSelectorsHtml = '';
407
408 2
		foreach ( $this->viewNames as $viewName ) {
409
410
			// cut off the selector label (if one was specified) from the actual view name
411 2
			$viewnameComponents = explode( '=', $viewName, 2 );
412
413 2
			$viewName = trim( $viewnameComponents[ 0 ] );
414
415 2
			if ( array_key_exists( $viewName, $this->mViewTypes ) ) {
416
417
				// generate unique id
418 2
				$viewid = $this->uniqid();
419
420 2
				if ( count( $viewnameComponents ) > 1 ) {
421
					// a selector label was specified in the wiki text
422
					$viewSelectorLabel = trim( $viewnameComponents[ 1 ] );
423
				} else {
424
					// use the default selector label
425 2
					$viewSelectorLabel = Message::get( 'srf-filtered-selectorlabel-' . $viewName );
426
				}
427
428
				/** @var \SRF\Filtered\View\View $view */
429 2
				$viewClassName = '\SRF\Filtered\View\\' . $this->mViewTypes[ $viewName ];
430 2
				$view = new $viewClassName( $resultItems, $this->parameters, $this, $viewSelectorLabel );
431
432 2
				$initErrorMsg = $view->getInitError();
433
434 2
				if ( $initErrorMsg !== null ) {
435 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...
436
				} else {
437
438 1
					$this->registerResourceModules( $view->getResourceModules() );
439
440 1
					$viewHtml .= Html::rawElement( 'div', [ 'id' => $viewid, 'class' => "filtered-view filtered-$viewName $viewid" ], $view->getResultText() );
441 1
					$viewSelectorsHtml .= Html::rawElement( 'div', [ 'class' => "filtered-view-selector filtered-$viewName $viewid" ], $viewSelectorLabel );
442
443 1
					foreach ( $resultItems as $row ) {
444 1
						$row->setData( $viewid, $view->getJsDataForRow( $row ) );
445
					}
446
447 2
					$config[ 'views' ][ $viewid ] = array_merge( [ 'type' => $viewName ], $view->getJsConfig() );
448
				}
449
			}
450
		}
451
452 2
		$viewHtml = Html::rawElement( 'div', [ 'class' => 'filtered-views', 'style' => 'display:none' ],
453 2
			Html::rawElement( 'div', [ 'class' => 'filtered-views-selectors-container', 'style' => 'display:none' ], $viewSelectorsHtml ) .
454 2
			Html::rawElement( 'div', [ 'class' => 'filtered-views-container' ], $viewHtml )
455
		);
456 2
		return [ $viewHtml, $config ];
457
	}
458
459
}