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
|
|||
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
|
|||
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 | } |
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: