Completed
Push — master ( 6dc7d8...407c40 )
by Karsten
15:45
created

formats/dygraphs/SRF_Dygraphs.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
 * A query printer that uses the dygraphs JavaScript library
5
 *
6
 * @see http://www.semantic-mediawiki.org/wiki/Help:Flot_timeseries_chart
7
 * @licence GNU GPL v2 or later
8
 *
9
 * @since 1.8
10
 *
11
 * @author mwjames
12
 */
13
class SRFDygraphs extends SMWResultPrinter {
14
15
	/**
16
	 * @see SMWResultPrinter::getName
17
	 * @return string
18
	 */
19
	public function getName() {
20
		return wfMessage( 'srf-printername-dygraphs' )->text();
21
	}
22
23
	/**
24
	 * @see SMWResultPrinter::getResultText
25
	 *
26
	 * @param SMWQueryResult $result
27
	 * @param $outputMode
28
	 *
29
	 * @return string
30
	 */
31
	protected function getResultText( SMWQueryResult $result, $outputMode ) {
32
33
		// Output mode is fixed
34
		$outputMode = SMW_OUTPUT_HTML;
35
36
		// Data processing
37
		$data = $this->getResultData( $result, $outputMode );
38
39
		// Post-data processing check
40
		if ( $data === [] ) {
41
			return $result->addErrors( [ wfMessage( 'srf-warn-empy-chart' )->inContentLanguage()->text() ] );
42
		} else {
43
			$options['sask'] = SRFUtils::htmlQueryResultLink( $this->getLink( $result, SMW_OUTPUT_HTML ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
44
			return $this->getFormatOutput( $data, $options );
45
		}
46
	}
47
48
	/**
49
	 * Returns an array with numerical data
50
	 *
51
	 * @since 1.8
52
	 *
53
	 * @param SMWQueryResult $result
54
	 * @param $outputMode
55
	 *
56
	 * @return array
57
	 */
58
	protected function getResultData( SMWQueryResult $result, $outputMode ) {
59
		$aggregatedValues = [];
60
61
		while ( $rows = $result->getNext() ) { // Objects (pages)
62
			$annotation = [];
63
			$dataSource = false;
64
65
			/**
66
			 * @var SMWResultArray $field
67
			 * @var SMWDataValue $dataValue
68
			 */
69
			foreach ( $rows as $field ) {
70
71
				// Use the subject marker to identify a possible data file
72
				$subject = $field->getResultSubject();
73
				if ( $this->params['datasource'] === 'file' && $subject->getTitle()->getNamespace(
74
					) === NS_FILE && !$dataSource ) {
75
					$aggregatedValues['subject'] = $this->makePageFromTitle( $subject->getTitle() )->getLongHTMLText(
76
						$this->getLinker( $field->getResultSubject() )
77
					);
78
					$aggregatedValues['url'] = wfFindFile( $subject->getTitle() )->getUrl();
79
					$dataSource = true;
80
				}
81
82
				// Proceed only where a label is known otherwise items are of no use
83
				// for being a potential object identifier
84
				if ( $field->getPrintRequest()->getLabel() !== '' ) {
85
					$propertyLabel = $field->getPrintRequest()->getLabel();
86
				} else {
87
					continue;
88
				}
89
90
				while ( ( $dataValue = $field->getNextDataValue() ) !== false ) { // Data values
91
92
					// Jump the column (indicated by continue) because we don't want the data source being part of the annotation array
93
					if ( $dataValue->getDataItem()->getDIType(
94
						) == SMWDataItem::TYPE_WIKIPAGE && $this->params['datasource'] === 'raw' && !$dataSource ) {
95
						// Support data source = raw which pulls the url from a wikipage in raw format
96
						$aggregatedValues['subject'] = $this->makePageFromTitle(
97
							$dataValue->getTitle()
98
						)->getLongHTMLText( $this->getLinker( $field->getResultSubject() ) );
99
						$aggregatedValues['url'] = $dataValue->getTitle()->getLocalURL( 'action=raw' );
100
						$dataSource = true;
101
						continue;
102
					} elseif ( $dataValue->getDataItem()->getDIType(
103
						) == SMWDataItem::TYPE_WIKIPAGE && $this->params['datasource'] === 'file' && $dataValue->getTitle(
104
						)->getNamespace() === NS_FILE && !$dataSource ) {
105
						// Support data source = file which pulls the url from a uploaded file
106
						$aggregatedValues['subject'] = $this->makePageFromTitle(
107
							$dataValue->getTitle()
108
						)->getLongHTMLText( $this->getLinker( $field->getResultSubject() ) );
109
						$aggregatedValues['url'] = wfFindFile( $dataValue->getTitle() )->getUrl();
110
						$dataSource = true;
111
						continue;
112
					} elseif ( $dataValue->getDataItem()->getDIType(
113
						) == SMWDataItem::TYPE_URI && $this->params['datasource'] === 'url' && !$dataSource ) {
114
						// Support data source = url, pointing to an url data source
115
						$aggregatedValues['link'] = $dataValue->getShortHTMLText( $this->getLinker( false ) );
116
						$aggregatedValues['url'] = $dataValue->getURL();
117
						$dataSource = true;
118
						continue;
119
					}
120
121
					// The annotation should adhere outlined conventions as the label identifies the array object key
122
					// series -> Required The name of the series to which the annotated point belongs
123
					// x -> Required The x value of the point
124
					// shortText -> Text that will appear as annotation flag
125
					// text -> A longer description of the annotation
126
					// @see  http://dygraphs.com/annotations.html
127
					if ( in_array( $propertyLabel, [ 'series', 'x', 'shortText', 'text' ] ) ) {
128
						if ( $dataValue->getDataItem()->getDIType() == SMWDataItem::TYPE_NUMBER ) {
129
							// Set unit if available
130
							$dataValue->setOutputFormat( $this->params['unit'] );
131
							// Check if unit is available
132
							$annotation[$propertyLabel] = $dataValue->getUnit() !== '' ? $dataValue->getShortWikiText(
133
							) : $dataValue->getNumber();
134
						} else {
135
							$annotation[$propertyLabel] = $dataValue->getWikiValue();
136
						}
137
					}
138
				}
139
			}
140
			// Sum-up collected row items in a single array
141
			if ( $annotation !== [] ) {
142
				$aggregatedValues['annotation'][] = $annotation;
143
			}
144
		}
145
		return $aggregatedValues;
146
	}
147
148
	private function makePageFromTitle( \Title $title ) {
149
		$dataValue = new SMWWikiPageValue( '_wpg' );
150
		$dataItem = SMWDIWikiPage::newFromTitle( $title );
151
		$dataValue->setDataItem( $dataItem );
152
		return $dataValue;
153
	}
154
155
	/**
156
	 * Prepare data for the output
157
	 *
158
	 * @since 1.8
159
	 *
160
	 * @param array $data
161
	 *
162
	 * @return string
163
	 */
164
	protected function getFormatOutput( $data, $options ) {
165
166
		// Object count
167
		static $statNr = 0;
168
		$chartID = 'srf-dygraphs-' . ++$statNr;
169
170
		$this->isHTML = true;
171
172
		// Reorganize the raw data
173
		if ( $this->params['datasource'] === 'page' ) {
174
			foreach ( $data as $key => $values ) {
175
				$dataObject[] = [ 'label' => $key, 'data' => $values ];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dataObject was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dataObject = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
176
			}
177
		} else {
178
			$dataObject['source'] = $data;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dataObject was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dataObject = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
179
		}
180
181
		// Prepare transfer array
182
		$chartData = [
183
			'data' => $dataObject,
0 ignored issues
show
The variable $dataObject 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...
184
			'sask' => $options['sask'],
185
			'parameters' => [
186
				'width' => $this->params['width'],
187
				'height' => $this->params['height'],
188
				'xlabel' => $this->params['xlabel'],
189
				'ylabel' => $this->params['ylabel'],
190
				'charttitle' => $this->params['charttitle'],
191
				'charttext' => $this->params['charttext'],
192
				'infotext' => $this->params['infotext'],
193
				'datasource' => $this->params['datasource'],
194
				'rollerperiod' => $this->params['mavg'],
195
				'gridview' => $this->params['gridview'],
196
				'errorbar' => $this->params['errorbar'],
197
			]
198
		];
199
200
		// Array encoding and output
201
		$requireHeadItem = [ $chartID => FormatJson::encode( $chartData ) ];
202
		SMWOutputs::requireHeadItem( $chartID, Skin::makeVariablesScript( $requireHeadItem ) );
203
204
		SMWOutputs::requireResource( 'ext.srf.dygraphs' );
205
206
		if ( $this->params['gridview'] === 'tabs' ) {
207
			SMWOutputs::requireResource( 'ext.srf.util.grid' );
208
		}
209
210
		// Chart/graph placeholder
211
		$chart = Html::rawElement(
212
			'div',
213
			[ 'id' => $chartID, 'class' => 'container', 'style' => "display:none;" ],
214
			null
215
		);
216
217
		// Processing/loading image
218
		$processing = SRFUtils::htmlProcessingElement( $this->isHTML );
219
220
		// Beautify class selector
221
		$class = $this->params['class'] ? ' ' . $this->params['class'] : ' dygraphs-common';
222
223
		// General output marker
224
		return Html::rawElement(
225
			'div',
226
			[ 'class' => 'srf-dygraphs' . $class ],
227
			$processing . $chart
228
		);
229
	}
230
231
	/**
232
	 * @see SMWResultPrinter::getParamDefinitions
233
	 *
234
	 * @since 1.8
235
	 *
236
	 * @param $definitions array of IParamDefinition
237
	 *
238
	 * @return array of IParamDefinition|array
239
	 */
240
	public function getParamDefinitions( array $definitions ) {
241
		$params = parent::getParamDefinitions( $definitions );
242
243
		$params['datasource'] = [
244
			'message' => 'srf-paramdesc-datasource',
245
			'default' => 'file',
246
			'values' => [ 'file', 'raw', 'url' ],
247
		];
248
249
		$params['errorbar'] = [
250
			'message' => 'srf-paramdesc-errorbar',
251
			'default' => '',
252
			'values' => [ 'fraction', 'sigma', 'range' ],
253
		];
254
255
		$params['min'] = [
256
			'type' => 'integer',
257
			'message' => 'srf-paramdesc-minvalue',
258
			'default' => '',
259
		];
260
261
		$params['mavg'] = [
262
			'type' => 'integer',
263
			'message' => 'srf-paramdesc-movingaverage',
264
			'default' => 14,
265
			'lowerbound' => 0,
266
		];
267
268
		$params['gridview'] = [
269
			'message' => 'srf-paramdesc-gridview',
270
			'default' => 'none',
271
			'values' => [ 'none', 'tabs' ],
272
		];
273
274
		$params['infotext'] = [
275
			'message' => 'srf-paramdesc-infotext',
276
			'default' => '',
277
		];
278
279
		$params['unit'] = [
280
			'message' => 'srf-paramdesc-unit',
281
			'default' => '',
282
		];
283
284
		$params['height'] = [
285
			'type' => 'integer',
286
			'message' => 'srf_paramdesc_chartheight',
287
			'default' => 400,
288
			'lowerbound' => 1,
289
		];
290
291
		$params['width'] = [
292
			'message' => 'srf_paramdesc_chartwidth',
293
			'default' => '100%',
294
		];
295
296
		$params['charttitle'] = [
297
			'message' => 'srf_paramdesc_charttitle',
298
			'default' => '',
299
		];
300
301
		$params['charttext'] = [
302
			'message' => 'srf-paramdesc-charttext',
303
			'default' => '',
304
		];
305
306
		$params['infotext'] = [
307
			'message' => 'srf-paramdesc-infotext',
308
			'default' => '',
309
		];
310
311
		$params['ylabel'] = [
312
			'message' => 'srf-paramdesc-yaxislabel',
313
			'default' => '',
314
		];
315
316
		$params['xlabel'] = [
317
			'message' => 'srf-paramdesc-xaxislabel',
318
			'default' => '',
319
		];
320
321
		$params['class'] = [
322
			'message' => 'srf-paramdesc-class',
323
			'default' => '',
324
		];
325
326
		return $params;
327
	}
328
}