1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Print query results in interactive timelines. |
4
|
|
|
* |
5
|
|
|
* @file SRF_Timeline.php |
6
|
|
|
* @ingroup SemanticResultFormats |
7
|
|
|
* |
8
|
|
|
* @author Markus Krötzsch |
9
|
|
|
* |
10
|
|
|
* FIXME: this code is just insane; rewrite from 0 is probably the only way to get it right |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Result printer for timeline data. |
15
|
|
|
* |
16
|
|
|
* @ingroup SemanticResultFormats |
17
|
|
|
*/ |
18
|
|
|
class SRFTimeline extends SMWResultPrinter { |
19
|
|
|
|
20
|
|
|
protected $m_tlstart = ''; // name of the start-date property if any |
21
|
|
|
protected $m_tlend = ''; // name of the end-date property if any |
22
|
|
|
protected $m_tlsize = ''; // CSS-compatible size (such as 400px) |
23
|
|
|
protected $m_tlbands = ''; // array of band IDs (MONTH, YEAR, ...) |
24
|
|
|
protected $m_tlpos = ''; // position identifier (start, end, today, middle) |
25
|
|
|
protected $mTemplate; |
26
|
|
|
protected $mNamedArgs; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @see SMWResultPrinter::handleParameters |
30
|
|
|
* |
31
|
|
|
* @since 1.6.3 |
32
|
|
|
* |
33
|
|
|
* @param array $params |
34
|
|
|
* @param $outputmode |
35
|
|
|
*/ |
36
|
1 |
|
protected function handleParameters( array $params, $outputmode ) { |
37
|
1 |
|
parent::handleParameters( $params, $outputmode ); |
38
|
|
|
|
39
|
1 |
|
$this->mTemplate = trim( $params['template'] ); |
40
|
1 |
|
$this->mNamedArgs = $params['named args']; |
41
|
1 |
|
$this->m_tlstart = smwfNormalTitleDBKey( $params['timelinestart'] ); |
42
|
1 |
|
$this->m_tlend = smwfNormalTitleDBKey( $params['timelineend'] ); |
43
|
1 |
|
$this->m_tlbands = $params['timelinebands']; |
44
|
1 |
|
$this->m_tlpos = strtolower( trim( $params['timelineposition'] ) ); |
45
|
|
|
|
46
|
|
|
// str_replace makes sure this is only one value, not mutliple CSS fields (prevent CSS attacks) |
47
|
|
|
// / FIXME: this is either unsafe or redundant, since Timeline is Wiki-compatible. If the JavaScript makes user inputs to CSS then it is bad even if we block this injection path. |
48
|
1 |
|
$this->m_tlsize = htmlspecialchars( str_replace( ';', ' ', strtolower( $params['timelinesize'] ) ) ); |
49
|
1 |
|
} |
50
|
|
|
|
51
|
|
|
public function getName() { |
52
|
|
|
// Give grep a chance to find the usages: |
53
|
|
|
// srf_printername_timeline, srf_printername_eventline |
54
|
|
|
return wfMessage( 'srf_printername_' . $this->mFormat )->text(); |
55
|
|
|
} |
56
|
|
|
|
57
|
1 |
|
protected function getResultText( SMWQueryResult $res, $outputmode ) { |
58
|
|
|
|
59
|
1 |
|
SMWOutputs::requireHeadItem( SMW_HEADER_STYLE ); |
60
|
1 |
|
SMWOutputs::requireResource( 'ext.srf.timeline' ); |
61
|
|
|
|
62
|
1 |
|
$isEventline = 'eventline' == $this->mFormat; |
63
|
1 |
|
$id = uniqid(); |
64
|
|
|
|
65
|
1 |
|
if ( !$isEventline && ( $this->m_tlstart == '' ) ) { // seek defaults |
66
|
1 |
|
foreach ( $res->getPrintRequests() as $pr ) { |
67
|
1 |
|
if ( ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) && ( $pr->getTypeID() == '_dat' ) ) { |
68
|
1 |
|
$dataValue = $pr->getData(); |
69
|
|
|
|
70
|
1 |
|
$date_value = $dataValue->getDataItem()->getLabel(); |
71
|
|
|
|
72
|
1 |
|
if ( ( $this->m_tlend == '' ) && ( $this->m_tlstart != '' ) && |
73
|
1 |
|
( $this->m_tlstart != $date_value ) ) { |
74
|
|
|
$this->m_tlend = $date_value; |
75
|
1 |
|
} elseif ( ( $this->m_tlstart == '' ) && ( $this->m_tlend != $date_value ) ) { |
76
|
1 |
|
$this->m_tlstart = $date_value; |
77
|
|
|
} |
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
// print header |
83
|
1 |
|
$result = "<div id=\"smwtimeline-$id\" class=\"smwtimeline is-disabled\" style=\"height: $this->m_tlsize\">"; |
84
|
1 |
|
$result .= '<span class="smw-overlay-spinner medium" style="top:40%; transform: translate(-50%, -50%);"></span>'; |
85
|
|
|
|
86
|
1 |
|
foreach ( $this->m_tlbands as $band ) { |
|
|
|
|
87
|
1 |
|
$result .= '<span class="smwtlband" style="display:none;">' . htmlspecialchars( $band ) . '</span>'; |
88
|
|
|
// just print any "band" given, the JavaScript will figure out what to make of it |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
// print all result rows |
92
|
1 |
|
if ( ( $this->m_tlstart != '' ) || $isEventline ) { |
93
|
1 |
|
$result .= $this->getEventsHTML( $res, $outputmode, $isEventline ); |
94
|
|
|
} |
95
|
|
|
// no further results displayed ... |
96
|
|
|
|
97
|
|
|
// print footer |
98
|
1 |
|
$result .= '</div>'; |
99
|
|
|
|
100
|
|
|
// yes, our code can be viewed as HTML if requested, no more parsing needed |
101
|
1 |
|
$this->isHTML = $outputmode == SMW_OUTPUT_HTML; |
102
|
|
|
|
103
|
1 |
|
return $result; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Returns the HTML for the events. |
108
|
|
|
* |
109
|
|
|
* @since 1.5.3 |
110
|
|
|
* |
111
|
|
|
* @param SMWQueryResult $res |
112
|
|
|
* @param $outputmode |
113
|
|
|
* @param boolean $isEventline |
114
|
|
|
* |
115
|
|
|
* @return string |
116
|
|
|
*/ |
117
|
1 |
|
protected function getEventsHTML( SMWQueryResult $res, $outputmode, $isEventline ) { |
118
|
1 |
|
global $curarticle, $cururl; // why not, code flow has reached max insanity already |
119
|
|
|
|
120
|
1 |
|
$positions = []; // possible positions, collected to select one for centering |
121
|
1 |
|
$curcolor = 0; // color cycling is used for eventline |
122
|
|
|
|
123
|
1 |
|
$result = ''; |
124
|
|
|
|
125
|
1 |
|
$output = false; // true if output for the popup was given on current line |
126
|
1 |
|
if ( $isEventline ) { |
127
|
|
|
$events = []; |
128
|
|
|
} // array of events that are to be printed |
129
|
|
|
|
130
|
1 |
|
while ( $row = $res->getNext() ) { // Loop over the objcts (pages) |
131
|
1 |
|
$hastime = false; // true as soon as some startdate value was found |
132
|
1 |
|
$hastitle = false; // true as soon as some label for the event was found |
133
|
1 |
|
$curdata = ''; // current *inner* print data (within some event span) |
134
|
1 |
|
$curmeta = ''; // current event meta data |
135
|
1 |
|
$cururl = ''; |
136
|
1 |
|
$curarticle = ''; // label of current article, if it was found; needed only for eventline labeling |
137
|
1 |
|
$first_col = true; |
138
|
|
|
|
139
|
1 |
|
if ( $this->mTemplate != '' ) { |
140
|
|
|
$this->hasTemplates = true; |
141
|
|
|
$template_text = ''; |
142
|
|
|
$i = 0; |
143
|
|
|
} |
144
|
|
|
|
145
|
1 |
|
foreach ( $row as $field ) { // Loop over the returned properties |
146
|
1 |
|
$first_value = true; |
147
|
1 |
|
$pr = $field->getPrintRequest(); |
148
|
1 |
|
$dataValue = $pr->getData(); |
149
|
|
|
|
150
|
1 |
|
if ( $dataValue == '' ) { |
151
|
1 |
|
$date_value = null; |
152
|
|
|
} else { |
153
|
1 |
|
$date_value = $dataValue->getDataItem()->getLabel(); |
154
|
|
|
} |
155
|
|
|
|
156
|
1 |
|
while ( ( $object = $field->getNextDataValue() ) !== false ) { // Loop over property values |
157
|
1 |
|
$event = $this->handlePropertyValue( |
158
|
1 |
|
$object, |
159
|
1 |
|
$outputmode, |
160
|
1 |
|
$pr, |
161
|
1 |
|
$first_col, |
162
|
1 |
|
$hastitle, |
163
|
1 |
|
$hastime, |
164
|
1 |
|
$first_value, |
165
|
1 |
|
$isEventline, |
166
|
1 |
|
$curmeta, |
167
|
1 |
|
$curdata, |
168
|
1 |
|
$date_value, |
169
|
1 |
|
$output, |
170
|
1 |
|
$positions |
171
|
|
|
); |
172
|
|
|
|
173
|
1 |
|
if ( $this->mTemplate != '' ) { |
174
|
|
|
$template_text .= '|' . ( $this->mNamedArgs ? '?' . $field->getPrintRequest()->getLabel( |
|
|
|
|
175
|
|
|
) : $i + 1 ) . '='; |
|
|
|
|
176
|
|
|
if ( !$first_value ) { |
177
|
|
|
$template_text .= ', '; |
178
|
|
|
} |
179
|
|
|
$template_text .= $object->getShortText( SMW_OUTPUT_WIKI, $this->getLinker( $first_value ) ); |
180
|
|
|
$i++; |
181
|
|
|
} |
182
|
|
|
|
183
|
1 |
|
if ( $event !== false ) { |
184
|
|
|
$events[] = $event; |
|
|
|
|
185
|
|
|
} |
186
|
|
|
|
187
|
1 |
|
$first_value = false; |
188
|
|
|
} |
189
|
|
|
|
190
|
1 |
|
if ( $output ) { |
191
|
1 |
|
$curdata .= '<br />'; |
192
|
|
|
} |
193
|
|
|
|
194
|
1 |
|
$output = false; |
195
|
1 |
|
$first_col = false; |
196
|
|
|
} |
197
|
|
|
|
198
|
1 |
|
if ( $this->mTemplate != '' ) { |
199
|
|
|
$curdata = '{{' . $this->mTemplate . $template_text . '}}'; |
200
|
|
|
} |
201
|
|
|
|
202
|
1 |
|
if ( $hastime ) { |
203
|
1 |
|
$result .= Html::rawElement( |
204
|
1 |
|
'span', |
205
|
1 |
|
[ 'class' => 'smwtlevent', 'style' => 'display:none;' ], |
206
|
1 |
|
$curmeta . Html::element( |
207
|
1 |
|
'span', |
208
|
1 |
|
[ 'class' => 'smwtlcoloricon' ], |
209
|
1 |
|
$curcolor |
210
|
1 |
|
) . $curdata |
211
|
|
|
); |
212
|
|
|
} |
213
|
|
|
|
214
|
1 |
|
if ( $isEventline ) { |
215
|
|
|
foreach ( $events as $event ) { |
216
|
|
|
$result .= '<span class="smwtlevent" style="display:none;" ><span class="smwtlstart">' . $event[0] . '</span><span class="smwtlurl">' . $curarticle . '</span><span class="smwtlcoloricon">' . $curcolor . '</span>'; |
217
|
|
|
if ( $curarticle != '' ) { |
218
|
|
|
$result .= '<span class="smwtlprefix">' . $curarticle . ' </span>'; |
219
|
|
|
} |
220
|
|
|
$result .= $curdata . '</span>'; |
221
|
|
|
$positions[$event[2]] = $event[0]; |
222
|
|
|
} |
223
|
|
|
$events = []; |
224
|
|
|
$curcolor = ( $curcolor + 1 ) % 10; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
228
|
1 |
|
if ( count( $positions ) > 0 ) { |
229
|
1 |
|
ksort( $positions ); |
230
|
1 |
|
$positions = array_values( $positions ); |
231
|
|
|
|
232
|
1 |
|
switch ( $this->m_tlpos ) { |
233
|
1 |
|
case 'start': |
234
|
|
|
$result .= '<span class="smwtlposition" style="display:none;" >' . $positions[0] . '</span>'; |
235
|
|
|
break; |
236
|
1 |
|
case 'end': |
237
|
|
|
$result .= '<span class="smwtlposition" style="display:none;" >' . $positions[count( |
238
|
|
|
$positions |
239
|
|
|
) - 1] . '</span>'; |
240
|
|
|
break; |
241
|
1 |
|
case 'today': |
242
|
|
|
break; // default |
243
|
1 |
|
case 'middle': |
244
|
|
|
default: |
245
|
1 |
|
$result .= '<span class="smwtlposition" style="display:none;" >' . $positions[ceil( |
246
|
1 |
|
count( $positions ) / 2 |
247
|
1 |
|
) - 1] . '</span>'; |
248
|
1 |
|
break; |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
1 |
|
return $result; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Hanldes a single property value. Returns an array with data for a single event or false. |
257
|
|
|
* |
258
|
|
|
* FIXME: 13 arguments, of which a whole bunch are byref... not a good design :) |
259
|
|
|
* |
260
|
|
|
* @since 1.5.3 |
261
|
|
|
* |
262
|
|
|
* @param SMWDataValue $object |
263
|
|
|
* @param $outputmode |
264
|
|
|
* @param SMWPrintRequest $pr |
265
|
|
|
* @param boolean $first_col |
266
|
|
|
* @param boolean &$hastitle |
267
|
|
|
* @param boolean &$hastime |
268
|
|
|
* @param boolean $first_value |
269
|
|
|
* @param boolean $isEventline |
270
|
|
|
* @param string &$curmeta |
271
|
|
|
* @param string &$curdata |
272
|
|
|
* @param &$date_value |
273
|
|
|
* @param boolean &$output |
274
|
|
|
* @param array &$positions |
275
|
|
|
* |
276
|
|
|
* @return false or array |
277
|
|
|
*/ |
278
|
1 |
|
protected function handlePropertyValue( SMWDataValue $object, $outputmode, SMWPrintRequest $pr, $first_col, |
279
|
|
|
&$hastitle, &$hastime, $first_value, $isEventline, &$curmeta, &$curdata, $date_value, &$output, array &$positions ) { |
280
|
1 |
|
global $curarticle, $cururl; |
281
|
|
|
|
282
|
1 |
|
$event = false; |
283
|
|
|
|
284
|
1 |
|
$l = $this->getLinker( $first_col ); |
285
|
|
|
|
286
|
1 |
|
if ( !$hastitle && $object->getTypeID( |
287
|
1 |
|
) != '_wpg' ) { // "linking" non-pages in title positions confuses timeline scripts, don't try this |
288
|
|
|
$l = null; |
289
|
|
|
} |
290
|
|
|
|
291
|
1 |
|
if ( $object->getTypeID() == '_wpg' ) { // use shorter "LongText" for wikipage |
292
|
1 |
|
$objectlabel = $object->getLongText( $outputmode, $l ); |
293
|
|
|
} else { |
294
|
1 |
|
$objectlabel = $object->getShortText( $outputmode, $l ); |
295
|
|
|
} |
296
|
|
|
|
297
|
1 |
|
$urlobject = ( $l !== null ); |
298
|
1 |
|
$header = ''; |
299
|
|
|
|
300
|
1 |
|
if ( $first_value ) { |
301
|
|
|
// find header for current value: |
302
|
1 |
|
if ( $this->mShowHeaders && ( '' != $pr->getLabel() ) ) { |
303
|
1 |
|
$header = $pr->getText( $outputmode, $this->mLinker ) . ': '; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
// is this a start date? |
307
|
1 |
|
if ( ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) && |
308
|
1 |
|
( $date_value == $this->m_tlstart ) ) { |
309
|
|
|
// FIXME: Timeline scripts should support XSD format explicitly. They |
310
|
|
|
// currently seem to implement iso8601 which deviates from XSD in cases. |
311
|
|
|
// NOTE: We can assume $object to be an SMWDataValue in this case. |
312
|
1 |
|
$curmeta .= Html::element( |
313
|
1 |
|
'span', |
314
|
1 |
|
[ 'class' => 'smwtlstart' ], |
315
|
1 |
|
$object->getXMLSchemaDate() |
|
|
|
|
316
|
|
|
); |
317
|
1 |
|
$positions[$object->getHash()] = $object->getXMLSchemaDate(); |
|
|
|
|
318
|
1 |
|
$hastime = true; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
// is this the end date? |
322
|
1 |
|
if ( ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) && |
323
|
1 |
|
( $date_value == $this->m_tlend ) ) { |
324
|
|
|
// NOTE: We can assume $object to be an SMWDataValue in this case. |
325
|
|
|
$curmeta .= Html::element( |
326
|
|
|
'span', |
327
|
|
|
[ 'class' => 'smwtlend' ], |
328
|
|
|
$object->getXMLSchemaDate( false ) |
|
|
|
|
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
// find title for displaying event |
333
|
1 |
|
if ( !$hastitle ) { |
334
|
1 |
|
$curmeta .= Html::rawElement( |
335
|
1 |
|
'span', |
336
|
|
|
[ |
337
|
1 |
|
'class' => $urlobject ? 'smwtlurl' : 'smwtltitle' |
338
|
|
|
], |
339
|
1 |
|
$objectlabel |
340
|
|
|
); |
341
|
|
|
|
342
|
1 |
|
if ( $pr->getMode() == SMWPrintRequest::PRINT_THIS ) { |
343
|
1 |
|
$curarticle = $object->getLongText( $outputmode, $l ); |
344
|
1 |
|
$cururl = $object->getTitle()->getFullUrl(); |
|
|
|
|
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
// NOTE: type Title of $object implied |
348
|
1 |
|
$hastitle = true; |
349
|
|
|
} |
350
|
|
|
} elseif ( $output ) { |
351
|
|
|
// it *can* happen that output is false here, if the subject was not printed (fixed subject query) and mutliple items appear in the first row |
352
|
|
|
$curdata .= ', '; |
353
|
|
|
} |
354
|
|
|
|
355
|
1 |
|
if ( !$first_col || !$first_value || $isEventline ) { |
356
|
1 |
|
$curdata .= $header . $objectlabel; |
357
|
1 |
|
$output = true; |
358
|
|
|
} |
359
|
|
|
|
360
|
1 |
|
if ( $isEventline && ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) && ( $pr->getTypeID( |
361
|
1 |
|
) == '_dat' ) && ( '' != $pr->getLabel( |
362
|
1 |
|
) ) && ( $date_value != $this->m_tlstart ) && ( $date_value != $this->m_tlend ) ) { |
363
|
|
|
$event = [ |
364
|
|
|
$object->getXMLSchemaDate(), |
|
|
|
|
365
|
|
|
$pr->getLabel(), |
366
|
|
|
$object->getDataItem()->getSortKey(), |
367
|
|
|
]; |
368
|
|
|
} |
369
|
|
|
|
370
|
1 |
|
return $event; |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* @see SMWResultPrinter::getParamDefinitions |
375
|
|
|
* |
376
|
|
|
* @since 1.8 |
377
|
|
|
* |
378
|
|
|
* @param $definitions array of IParamDefinition |
379
|
|
|
* |
380
|
|
|
* @return array of IParamDefinition|array |
381
|
|
|
*/ |
382
|
1 |
|
public function getParamDefinitions( array $definitions ) { |
383
|
1 |
|
$params = parent::getParamDefinitions( $definitions ); |
384
|
|
|
|
385
|
1 |
|
$params['timelinesize'] = [ |
386
|
|
|
'default' => '300px', |
387
|
|
|
'message' => 'srf_paramdesc_timelinesize', |
388
|
|
|
]; |
389
|
|
|
|
390
|
1 |
|
$params['timelineposition'] = [ |
391
|
|
|
'default' => 'middle', |
392
|
|
|
'message' => 'srf_paramdesc_timelineposition', |
393
|
|
|
'values' => [ 'start', 'middle', 'end', 'today' ], |
394
|
|
|
]; |
395
|
|
|
|
396
|
1 |
|
$params['timelinestart'] = [ |
397
|
|
|
'default' => '', |
398
|
|
|
'message' => 'srf_paramdesc_timelinestart', |
399
|
|
|
]; |
400
|
|
|
|
401
|
1 |
|
$params['timelineend'] = [ |
402
|
|
|
'default' => '', |
403
|
|
|
'message' => 'srf_paramdesc_timelineend', |
404
|
|
|
]; |
405
|
|
|
|
406
|
1 |
|
$params['timelinebands'] = [ |
407
|
|
|
'islist' => true, |
408
|
|
|
'default' => [ 'MONTH', 'YEAR' ], |
409
|
|
|
'message' => 'srf_paramdesc_timelinebands', |
410
|
|
|
'values' => [ 'MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR', 'DECADE' ], |
411
|
|
|
]; |
412
|
|
|
|
413
|
1 |
|
$params['template'] = [ |
414
|
|
|
'message' => 'smw-paramdesc-template', |
415
|
|
|
'default' => '', |
416
|
|
|
]; |
417
|
|
|
|
418
|
1 |
|
$params['named args'] = [ |
419
|
|
|
'type' => 'boolean', |
420
|
|
|
'message' => 'smw-paramdesc-named_args', |
421
|
|
|
'default' => false, |
422
|
|
|
]; |
423
|
|
|
|
424
|
1 |
|
return $params; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
} |
428
|
|
|
|