SRFDygraphs   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 318
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 5
dl 0
loc 318
ccs 0
cts 116
cp 0
rs 9.76
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 3 1
A getResultText() 0 16 2
D getResultData() 0 91 23
A makePageFromTitle() 0 6 1
B getFormatOutput() 0 66 5
B getParamDefinitions() 0 88 1
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 mwjames
$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
					$dataItem = $dataValue->getDataItem();
94
95
					if ( $dataItem->getDIType() === SMWDataItem::TYPE_WIKIPAGE ) {
96
						$title = $dataItem->getTitle();
97
					}
98
99
					if ( $dataItem->getDIType() == SMWDataItem::TYPE_WIKIPAGE && $this->params['datasource'] === 'raw' && !$dataSource ) {
100
						// Support data source = raw which pulls the url from a wikipage in raw format
101
						$aggregatedValues['subject'] = $this->makePageFromTitle(
102
							$title
0 ignored issues
show
Bug introduced by Stephan
The variable $title 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...
103
						)->getLongHTMLText( $this->getLinker( $field->getResultSubject() ) );
104
						$aggregatedValues['url'] = $title->getLocalURL( 'action=raw' );
105
						$dataSource = true;
106
						continue;
107
					} elseif ( $dataItem->getDIType() == SMWDataItem::TYPE_WIKIPAGE && $this->params['datasource'] === 'file' && $title->getNamespace() === NS_FILE && !$dataSource ) {
108
						// Support data source = file which pulls the url from a uploaded file
109
						$aggregatedValues['subject'] = $this->makePageFromTitle(
110
							$title
111
						)->getLongHTMLText( $this->getLinker( $field->getResultSubject() ) );
112
						$aggregatedValues['url'] = wfFindFile( $title )->getUrl();
113
						$dataSource = true;
114
						continue;
115
					} elseif ( $dataItem->getDIType() == SMWDataItem::TYPE_URI && $this->params['datasource'] === 'url' && !$dataSource ) {
116
						// Support data source = url, pointing to an url data source
117
						$aggregatedValues['link'] = $dataValue->getShortHTMLText( $this->getLinker( false ) );
118
						$aggregatedValues['url'] = $dataValue->getURL();
119
						$dataSource = true;
120
						continue;
121
					}
122
123
					// The annotation should adhere outlined conventions as the label identifies the array object key
124
					// series -> Required The name of the series to which the annotated point belongs
125
					// x -> Required The x value of the point
126
					// shortText -> Text that will appear as annotation flag
127
					// text -> A longer description of the annotation
128
					// @see  http://dygraphs.com/annotations.html
129
					if ( in_array( $propertyLabel, [ 'series', 'x', 'shortText', 'text' ] ) ) {
130
						if ( $dataItem->getDIType() == SMWDataItem::TYPE_NUMBER ) {
131
							// Set unit if available
132
							$dataValue->setOutputFormat( $this->params['unit'] );
133
							// Check if unit is available
134
							$annotation[$propertyLabel] = $dataValue->getUnit() !== '' ? $dataValue->getShortWikiText(
135
							) : $dataValue->getNumber();
136
						} else {
137
							$annotation[$propertyLabel] = $dataValue->getWikiValue();
138
						}
139
					}
140
				}
141
			}
142
			// Sum-up collected row items in a single array
143
			if ( $annotation !== [] ) {
144
				$aggregatedValues['annotation'][] = $annotation;
145
			}
146
		}
147
		return $aggregatedValues;
148
	}
149
150
	private function makePageFromTitle( \Title $title ) {
151
		$dataValue = new SMWWikiPageValue( '_wpg' );
152
		$dataItem = SMWDIWikiPage::newFromTitle( $title );
153
		$dataValue->setDataItem( $dataItem );
154
		return $dataValue;
155
	}
156
157
	/**
158
	 * Prepare data for the output
159
	 *
160
	 * @since 1.8
161
	 *
162
	 * @param array $data
163
	 *
164
	 * @return string
165
	 */
166
	protected function getFormatOutput( $data, $options ) {
167
168
		// Object count
169
		static $statNr = 0;
170
		$chartID = 'srf-dygraphs-' . ++$statNr;
171
172
		$this->isHTML = true;
173
174
		// Reorganize the raw data
175
		if ( $this->params['datasource'] === 'page' ) {
176
			foreach ( $data as $key => $values ) {
177
				$dataObject[] = [ 'label' => $key, 'data' => $values ];
0 ignored issues
show
Coding Style Comprehensibility introduced by kghbln
$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...
178
			}
179
		} else {
180
			$dataObject['source'] = $data;
0 ignored issues
show
Coding Style Comprehensibility introduced by mwjames
$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...
181
		}
182
183
		// Prepare transfer array
184
		$chartData = [
185
			'data' => $dataObject,
0 ignored issues
show
Bug introduced by mwjames
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...
186
			'sask' => $options['sask'],
187
			'parameters' => [
188
				'width' => $this->params['width'],
189
				'height' => $this->params['height'],
190
				'xlabel' => $this->params['xlabel'],
191
				'ylabel' => $this->params['ylabel'],
192
				'charttitle' => $this->params['charttitle'],
193
				'charttext' => $this->params['charttext'],
194
				'infotext' => $this->params['infotext'],
195
				'datasource' => $this->params['datasource'],
196
				'rollerperiod' => $this->params['mavg'],
197
				'gridview' => $this->params['gridview'],
198
				'errorbar' => $this->params['errorbar'],
199
			]
200
		];
201
202
		// Array encoding and output
203
		$requireHeadItem = [ $chartID => FormatJson::encode( $chartData ) ];
204
		SMWOutputs::requireHeadItem( $chartID, SRFUtils::makeVariablesScript( $requireHeadItem ) );
205
206
		SMWOutputs::requireResource( 'ext.srf.dygraphs' );
207
208
		if ( $this->params['gridview'] === 'tabs' ) {
209
			SMWOutputs::requireResource( 'ext.srf.util.grid' );
210
		}
211
212
		// Chart/graph placeholder
213
		$chart = Html::rawElement(
214
			'div',
215
			[ 'id' => $chartID, 'class' => 'container', 'style' => "display:none;" ],
216
			null
217
		);
218
219
		// Processing/loading image
220
		$processing = SRFUtils::htmlProcessingElement( $this->isHTML );
221
222
		// Beautify class selector
223
		$class = $this->params['class'] ? ' ' . $this->params['class'] : ' dygraphs-common';
224
225
		// General output marker
226
		return Html::rawElement(
227
			'div',
228
			[ 'class' => 'srf-dygraphs' . $class ],
229
			$processing . $chart
230
		);
231
	}
232
233
	/**
234
	 * @see SMWResultPrinter::getParamDefinitions
235
	 *
236
	 * @since 1.8
237
	 *
238
	 * @param $definitions array of IParamDefinition
239
	 *
240
	 * @return array of IParamDefinition|array
241
	 */
242
	public function getParamDefinitions( array $definitions ) {
243
		$params = parent::getParamDefinitions( $definitions );
244
245
		$params['datasource'] = [
246
			'message' => 'srf-paramdesc-datasource',
247
			'default' => 'file',
248
			'values' => [ 'file', 'raw', 'url' ],
249
		];
250
251
		$params['errorbar'] = [
252
			'message' => 'srf-paramdesc-errorbar',
253
			'default' => '',
254
			'values' => [ 'fraction', 'sigma', 'range' ],
255
		];
256
257
		$params['min'] = [
258
			'type' => 'integer',
259
			'message' => 'srf-paramdesc-minvalue',
260
			'default' => '',
261
		];
262
263
		$params['mavg'] = [
264
			'type' => 'integer',
265
			'message' => 'srf-paramdesc-movingaverage',
266
			'default' => 14,
267
			'lowerbound' => 0,
268
		];
269
270
		$params['gridview'] = [
271
			'message' => 'srf-paramdesc-gridview',
272
			'default' => 'none',
273
			'values' => [ 'none', 'tabs' ],
274
		];
275
276
		$params['infotext'] = [
277
			'message' => 'srf-paramdesc-infotext',
278
			'default' => '',
279
		];
280
281
		$params['unit'] = [
282
			'message' => 'srf-paramdesc-unit',
283
			'default' => '',
284
		];
285
286
		$params['height'] = [
287
			'type' => 'integer',
288
			'message' => 'srf_paramdesc_chartheight',
289
			'default' => 400,
290
			'lowerbound' => 1,
291
		];
292
293
		$params['width'] = [
294
			'message' => 'srf_paramdesc_chartwidth',
295
			'default' => '100%',
296
		];
297
298
		$params['charttitle'] = [
299
			'message' => 'srf_paramdesc_charttitle',
300
			'default' => '',
301
		];
302
303
		$params['charttext'] = [
304
			'message' => 'srf-paramdesc-charttext',
305
			'default' => '',
306
		];
307
308
		$params['infotext'] = [
309
			'message' => 'srf-paramdesc-infotext',
310
			'default' => '',
311
		];
312
313
		$params['ylabel'] = [
314
			'message' => 'srf-paramdesc-yaxislabel',
315
			'default' => '',
316
		];
317
318
		$params['xlabel'] = [
319
			'message' => 'srf-paramdesc-xaxislabel',
320
			'default' => '',
321
		];
322
323
		$params['class'] = [
324
			'message' => 'srf-paramdesc-class',
325
			'default' => '',
326
		];
327
328
		return $params;
329
	}
330
}
331