Completed
Push — master ( ac15b4...98e0ae )
by Jeroen De
38:18 queued 18:19
created

formats/timeline/SRF_Timeline.php (2 issues)

Labels
Severity

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
 * 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
 * @ingroup SemanticResultFormats
16
 */
17
class SRFTimeline extends SMWResultPrinter {
18
19
	protected $m_tlstart = '';  // name of the start-date property if any
20
	protected $m_tlend = '';  // name of the end-date property if any
21
	protected $m_tlsize = ''; // CSS-compatible size (such as 400px)
22
	protected $m_tlbands = ''; // array of band IDs (MONTH, YEAR, ...)
23
	protected $m_tlpos = ''; // position identifier (start, end, today, middle)
24
	protected $mTemplate;
25
	protected $mNamedArgs;
26
27
	/**
28
	 * @see SMWResultPrinter::handleParameters
29
	 *
30
	 * @since 1.6.3
31
	 *
32
	 * @param array $params
33
	 * @param $outputmode
34
	 */
35 1
	protected function handleParameters( array $params, $outputmode ) {
36 1
		parent::handleParameters( $params, $outputmode );
37
38 1
		$this->mTemplate = trim( $params['template'] );
39 1
		$this->mNamedArgs = $params['named args'];
40 1
		$this->m_tlstart = smwfNormalTitleDBKey( $params['timelinestart'] );
41 1
		$this->m_tlend = smwfNormalTitleDBKey( $params['timelineend'] );
42 1
		$this->m_tlbands = $params['timelinebands'];
43 1
		$this->m_tlpos = strtolower( trim( $params['timelineposition'] ) );
44
45
			// str_replace makes sure this is only one value, not mutliple CSS fields (prevent CSS attacks)
46
			// / 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.
47 1
		$this->m_tlsize = htmlspecialchars( str_replace( ';', ' ', strtolower( $params['timelinesize'] ) ) );
48 1
	}
49
50
	public function getName() {
51
		// Give grep a chance to find the usages:
52
		// srf_printername_timeline, srf_printername_eventline
53
		return wfMessage( 'srf_printername_' . $this->mFormat )->text();
54
	}
55
56 1
	protected function getResultText( SMWQueryResult $res, $outputmode ) {
57
58 1
		SMWOutputs::requireHeadItem( SMW_HEADER_STYLE );
59 1
		SMWOutputs::requireResource( 'ext.srf.timeline' );
60
61 1
		$isEventline = 'eventline' == $this->mFormat;
62 1
		$id = uniqid();
63
64 1
		if ( !$isEventline && ( $this->m_tlstart == '' ) ) { // seek defaults
65 1
			foreach ( $res->getPrintRequests() as $pr ) {
66 1
				if ( ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) && ( $pr->getTypeID() == '_dat' ) ) {
67 1
					$dataValue = $pr->getData();
68
69 1
					$date_value = $dataValue->getDataItem()->getLabel();
70
71 1
					if ( ( $this->m_tlend == '' ) && ( $this->m_tlstart != '' ) &&
72 1
					     ( $this->m_tlstart != $date_value ) ) {
73
						$this->m_tlend = $date_value;
74 1
					} elseif ( ( $this->m_tlstart == '' ) && ( $this->m_tlend != $date_value ) ) {
75 1
						$this->m_tlstart = $date_value;
76
					}
77
				}
78
			}
79
		}
80
81
		// print header
82 1
		$result = "<div id=\"smwtimeline-$id\" class=\"smwtimeline is-disabled\" style=\"height: $this->m_tlsize\">";
83 1
		$result .= '<span class="smw-overlay-spinner medium" style="top:40%; transform: translate(-50%, -50%);"></span>';
84
85 1
		foreach ( $this->m_tlbands as $band ) {
86 1
			$result .= '<span class="smwtlband" style="display:none;">' . htmlspecialchars( $band ) . '</span>';
87
			// just print any "band" given, the JavaScript will figure out what to make of it
88
		}
89
90
		// print all result rows
91 1
		if ( ( $this->m_tlstart != '' ) || $isEventline ) {
92 1
			$result .= $this->getEventsHTML( $res, $outputmode, $isEventline );
93
		}
94
		// no further results displayed ...
95
96
		// print footer
97 1
		$result .= '</div>';
98
99
		// yes, our code can be viewed as HTML if requested, no more parsing needed
100 1
		$this->isHTML = $outputmode == SMW_OUTPUT_HTML;
101
102 1
		return $result;
103
	}
104
105
	/**
106
	 * Returns the HTML for the events.
107
	 *
108
	 * @since 1.5.3
109
	 *
110
	 * @param SMWQueryResult $res
111
	 * @param $outputmode
112
	 * @param boolean $isEventline
113
	 *
114
	 * @return string
115
	 */
116 1
	protected function getEventsHTML( SMWQueryResult $res, $outputmode, $isEventline ) {
117 1
		global $curarticle, $cururl; // why not, code flow has reached max insanity already
118
119 1
		$positions = []; // possible positions, collected to select one for centering
120 1
		$curcolor = 0; // color cycling is used for eventline
121
122 1
		$result = '';
123
124 1
		$output = false; // true if output for the popup was given on current line
125 1
		if ( $isEventline ) $events = []; // array of events that are to be printed
126
127 1
		while ( $row = $res->getNext() ) { // Loop over the objcts (pages)
128 1
			$hastime = false; // true as soon as some startdate value was found
129 1
			$hastitle = false; // true as soon as some label for the event was found
130 1
			$curdata = ''; // current *inner* print data (within some event span)
131 1
			$curmeta = ''; // current event meta data
132 1
			$cururl = '';
133 1
			$curarticle = ''; // label of current article, if it was found; needed only for eventline labeling
134 1
			$first_col = true;
135
136 1
			if ( $this->mTemplate != '' ) {
137
				$this->hasTemplates = true;
138
				$template_text = '';
139
				$i = 0;
140
			}
141
142
			foreach ( $row as $field ) { // Loop over the returned properties
143 1
				$first_value = true;
144 1
				$pr = $field->getPrintRequest();
145 1
				$dataValue = $pr->getData();
146 1
147
				if ( $dataValue == '' ) {
148 1
					$date_value = null;
149 1
				}
150
				else {
151
					$date_value = $dataValue->getDataItem()->getLabel();
152 1
				}
153
154
				while ( ( $object = $field->getNextDataValue() ) !== false ) { // Loop over property values
155 1
					$event = $this->handlePropertyValue(
156 1
						$object, $outputmode, $pr, $first_col, $hastitle, $hastime,
157 1
						$first_value, $isEventline, $curmeta, $curdata, $date_value, $output, $positions
158 1
					);
159
160
					if ( $this->mTemplate != '')
161 1
					{
162
						$template_text .= '|' . ( $this->mNamedArgs ? '?' . $field->getPrintRequest()->getLabel() : $i + 1 ) . '=';
0 ignored issues
show
The variable $template_text does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
The variable $i does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
163
						if ( !$first_value ) {
164
							$template_text .= ', ';
165
						}
166
						$template_text .= $object->getShortText( SMW_OUTPUT_WIKI, $this->getLinker( $first_value ) );
167
						$i++;
168
					}
169
170
					if ( $event !== false ) {
171 1
						$events[] = $event;
172
					}
173
174
					$first_value = false;
175 1
				}
176
177
				if ( $output ) {
178 1
					$curdata .= '<br />';
179 1
				}
180
181
				$output = false;
182 1
				$first_col = false;
183 1
			}
184
185
			if ( $this->mTemplate != '')
186 1
			{
187
				$curdata = '{{' . $this->mTemplate . $template_text . '}}';
188
			}
189
190
			if ( $hastime ) {
191 1
				$result .= Html::rawElement(
192 1
					'span',
193 1
					[ 'class' => 'smwtlevent', 'style' => 'display:none;' ],
194 1
					$curmeta . Html::element(
195 1
						'span',
196 1
						[ 'class' => 'smwtlcoloricon' ],
197 1
						$curcolor
198 1
					) . $curdata
199 1
				);
200
			}
201
202
			if ( $isEventline ) {
203 1
				foreach ( $events as $event ) {
204
					$result .= '<span class="smwtlevent" style="display:none;" ><span class="smwtlstart">' . $event[0] . '</span><span class="smwtlurl">' . $curarticle . '</span><span class="smwtlcoloricon">' . $curcolor . '</span>';
205
					if ( $curarticle != '' ) $result .= '<span class="smwtlprefix">' . $curarticle . ' </span>';
206
					$result .=  $curdata . '</span>';
207
					$positions[$event[2]] = $event[0];
208
				}
209
				$events = [];
210
				$curcolor = ( $curcolor + 1 ) % 10;
211
			}
212
		}
213
214
		if ( count( $positions ) > 0 ) {
215 1
			ksort( $positions );
216 1
			$positions = array_values( $positions );
217 1
218
			switch ( $this->m_tlpos ) {
219 1
				case 'start':
220 1
					$result .= '<span class="smwtlposition" style="display:none;" >' . $positions[0] . '</span>';
221
					break;
222
				case 'end':
223 1
					$result .= '<span class="smwtlposition" style="display:none;" >' . $positions[count( $positions ) - 1] . '</span>';
224
					break;
225
				case 'today': break; // default
226 1
				case 'middle': default:
227 1
					$result .= '<span class="smwtlposition" style="display:none;" >' . $positions[ceil( count( $positions ) / 2 ) - 1] . '</span>';
228 1
					break;
229 1
			}
230
		}
231
232
		return $result;
233 1
	}
234
235
	/**
236
	 * Hanldes a single property value. Returns an array with data for a single event or false.
237
	 *
238
	 * FIXME: 13 arguments, of which a whole bunch are byref... not a good design :)
239
	 *
240
	 * @since 1.5.3
241
	 *
242
	 * @param SMWDataValue $object
243
	 * @param $outputmode
244
	 * @param SMWPrintRequest $pr
245
	 * @param boolean $first_col
246
	 * @param boolean &$hastitle
247
	 * @param boolean &$hastime
248
	 * @param boolean $first_value
249
	 * @param boolean $isEventline
250
	 * @param string &$curmeta
251
	 * @param string &$curdata
252
	 * @param &$date_value
253
	 * @param boolean &$output
254
	 * @param array &$positions
255
	 *
256
	 * @return false or array
257
	 */
258
	protected function handlePropertyValue( SMWDataValue $object, $outputmode, SMWPrintRequest $pr, $first_col,
259 1
		&$hastitle, &$hastime, $first_value, $isEventline, &$curmeta, &$curdata, $date_value, &$output, array &$positions ) {
260
			global $curarticle, $cururl;
261 1
262
		$event = false;
263 1
264
		$l = $this->getLinker( $first_col );
265 1
266
		if ( !$hastitle && $object->getTypeID() != '_wpg' ) { // "linking" non-pages in title positions confuses timeline scripts, don't try this
267 1
			$l = null;
268
		}
269
270
		if ( $object->getTypeID() == '_wpg' ) { // use shorter "LongText" for wikipage
271 1
			$objectlabel = $object->getLongText( $outputmode, $l );
272 1
		} else {
273
			$objectlabel = $object->getShortText( $outputmode, $l );
274 1
		}
275
276
		$urlobject =  ( $l !== null );
277 1
		$header = '';
278 1
279
		if ( $first_value ) {
280 1
			// find header for current value:
281
			if ( $this->mShowHeaders && ( '' != $pr->getLabel() ) ) {
282 1
				$header = $pr->getText( $outputmode, $this->mLinker ) . ': ';
283 1
			}
284
285
			// is this a start date?
286
			if ( ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) &&
287 1
			     ( $date_value == $this->m_tlstart ) ) {
288 1
				// FIXME: Timeline scripts should support XSD format explicitly. They
289
				// currently seem to implement iso8601 which deviates from XSD in cases.
290
				// NOTE: We can assume $object to be an SMWDataValue in this case.
291
				$curmeta .= Html::element(
292 1
					'span',
293 1
					[ 'class' => 'smwtlstart' ],
294 1
					$object->getXMLSchemaDate()
295 1
				);
296
				$positions[$object->getHash()] = $object->getXMLSchemaDate();
297 1
				$hastime = true;
298 1
			}
299
300
			// is this the end date?
301
			if ( ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) &&
302 1
			     ( $date_value == $this->m_tlend ) ) {
303 1
				// NOTE: We can assume $object to be an SMWDataValue in this case.
304
				$curmeta .= Html::element(
305
					'span',
306
					[ 'class' => 'smwtlend' ],
307
					$object->getXMLSchemaDate( false )
308
				);
309
			}
310
311
			// find title for displaying event
312
			if ( !$hastitle ) {
313 1
				$curmeta .= Html::rawElement(
314 1
					'span',
315 1
					[
316
						'class' => $urlobject ? 'smwtlurl' : 'smwtltitle'
317 1
					],
318
					$objectlabel
319 1
				);
320
321
				if ( $pr->getMode() == SMWPrintRequest::PRINT_THIS ) {
322 1
					$curarticle = $object->getLongText( $outputmode, $l );
323 1
					$cururl = $object->getTitle()->getFullUrl();
324 1
				}
325
326
				// NOTE: type Title of $object implied
327
				$hastitle = true;
328 1
			}
329
		} elseif ( $output ) {
330
			// 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
331
			$curdata .= ', ';
332
		}
333
334
		if ( !$first_col || !$first_value || $isEventline ) {
335 1
			$curdata .= $header . $objectlabel;
336 1
			$output = true;
337 1
		}
338
339
		if ( $isEventline && ( $pr->getMode() == SMWPrintRequest::PRINT_PROP ) && ( $pr->getTypeID() == '_dat' ) && ( '' != $pr->getLabel() ) && ( $date_value != $this->m_tlstart ) && ( $date_value != $this->m_tlend ) ) {
340 1
			$event = [
341
				$object->getXMLSchemaDate(),
342
				$pr->getLabel(),
343
				$object->getDataItem()->getSortKey(),
344
			];
345
		}
346
347
		return $event;
348 1
	}
349
350
	/**
351
	 * @see SMWResultPrinter::getParamDefinitions
352
	 *
353
	 * @since 1.8
354
	 *
355
	 * @param $definitions array of IParamDefinition
356
	 *
357
	 * @return array of IParamDefinition|array
358
	 */
359
	public function getParamDefinitions( array $definitions ) {
360 1
		$params = parent::getParamDefinitions( $definitions );
361 1
362
		$params['timelinesize'] = [
363 1
			'default' => '300px',
364
			'message' => 'srf_paramdesc_timelinesize',
365
		];
366
367
		$params['timelineposition'] = [
368 1
			'default' => 'middle',
369
			'message' => 'srf_paramdesc_timelineposition',
370
			'values' => [ 'start', 'middle', 'end', 'today' ],
371
		];
372
373
		$params['timelinestart'] = [
374 1
			'default' => '',
375
			'message' => 'srf_paramdesc_timelinestart',
376
		];
377
378
		$params['timelineend'] = [
379 1
			'default' => '',
380
			'message' => 'srf_paramdesc_timelineend',
381
		];
382
383
		$params['timelinebands'] = [
384 1
			'islist' => true,
385
			'default' => [ 'MONTH', 'YEAR' ],
386
			'message' => 'srf_paramdesc_timelinebands',
387
			'values' => [ 'MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR', 'DECADE' ],
388
		];
389
390
		$params['template'] = [
391 1
			'message' => 'smw-paramdesc-template',
392
			'default' => '',
393
		];
394
395
		$params['named args'] = [
396 1
			'type' => 'boolean',
397
			'message' => 'smw-paramdesc-named_args',
398
			'default' => false,
399
		];
400
401
402
		return $params;
403 1
	}
404
405
}
406