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() { |
|
|
|
|
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
|
|
|
} |
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: