Completed
Push — master ( da9f32...bb055b )
by Karsten
16:00
created

SRFjqPlotSeries::getParamDefinitions()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 58
ccs 0
cts 10
cp 0
rs 8.9163
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * A query printer for charts series using the jqPlot JavaScript library.
5
 *
6
 * @since 1.8
7
 * @licence GNU GPL v2 or later
8
 *
9
 * @author mwjames
10
 */
11
class SRFjqPlotSeries extends SMWResultPrinter {
12
13
	/**
14
	 * @see SMWResultPrinter::getName
15
	 */
16
	public function getName() {
17
		return wfMessage( 'srf-printername-jqplotseries' )->text();
18
	}
19
20
	/**
21
	 * Returns an array with the numerical data in the query result.
22
	 *
23
	 *
24
	 * @param SMWQueryResult $result
25
	 * @param $outputMode
26
	 *
27
	 * @return string
28
	 */
29
	protected function getResultText( SMWQueryResult $result, $outputMode ) {
30
31
		// Get data set
32
		$data = $this->getResultData( $result, $outputMode );
33
34
		// Check data availability
35
		if ( $data['series'] === [] ) {
36
			return $result->addErrors(
37
				[
38
					wfMessage( 'srf-warn-empy-chart' )
39
						->inContentLanguage()->text() ]
40
			);
41
		} else {
42
			$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...
43
			return $this->getFormatOutput( $this->getFormatSettings( $this->getNumbersTicks( $data ), $options ) );
44
		}
45
	}
46
47
	/**
48
	 * Returns an array with the numerical data
49
	 *
50
	 * @since 1.8
51
	 *
52
	 * @param SMWQueryResult $result
0 ignored issues
show
Bug introduced by
There is no parameter named $result. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
53
	 * @param $outputMode
54
	 *
55
	 * @return array
56
	 */
57
	protected function getResultData( SMWQueryResult $res, $outputMode ) {
58
		$data = [];
59
		$data['series'] = [];
60
61
		while ( $row = $res->getNext() ) {
62
			// Loop over their fields (properties)
63
			$label = '';
64
			$i = 0;
65
66
			foreach ( $row as /* SMWResultArray */
67
					  $field ) {
68
				$i++;
69
				$rowNumbers = [];
70
71
				// Grouping by subject (page object) or property
72
				if ( $this->params['group'] === 'subject' ) {
73
					$groupedBy = $field->getResultSubject()->getTitle()->getText();
74
				} else {
75
					$groupedBy = $field->getPrintRequest()->getLabel();
76
				}
77
78
				// Property label
79
				$property = $field->getPrintRequest()->getLabel();
80
81
				// First column property typeid
82
				$i == 1 ? $data['fcolumntypeid'] = $field->getPrintRequest()->getTypeID() : '';
83
84
				// Loop over all values for the property.
85
				while ( ( /* SMWDataValue */
86
					$object = $field->getNextDataValue() ) !== false ) {
87
88
					if ( $object->getDataItem()->getDIType() == SMWDataItem::TYPE_NUMBER ) {
89
						$number = $object->getNumber();
90
91
						// Checking against the row and in case the first column is a numeric
92
						// value it is handled as label with the remaining steps continue to work
93
						// as it were a text label
94
						// The first column container will not be part of the series container
95
						if ( $i == 1 ) {
96
							$label = $number;
97
							continue;
98
						}
99
100
						if ( $label !== '' && $number >= $this->params['min'] ) {
101
102
							// Reference array summarize all items per row
103
							$rowNumbers += [ 'subject' => $label, 'value' => $number, 'property' => $property ];
104
105
							// Store plain numbers for simpler handling
106
							$data['series'][$groupedBy][] = $number;
107
						}
108
					} elseif ( $object->getDataItem()->getDIType() == SMWDataItem::TYPE_TIME ) {
109
						$label = $object->getShortWikiText();
110
					} else {
111
						$label = $object->getWikiValue();
112
					}
113
				}
114
				// Only for array's with numbers
115
				if ( count( $rowNumbers ) > 0 ) {
116
117
					// For cases where mainlabel=- we assume that the subject should not be
118
					// used as identifier and therefore we try to match the groupby
119
					// with the first available text label
120
					if ( $this->params['mainlabel'] == '-' && $this->params['group'] === 'subject' ) {
121
						$data[$this->params['group']][$label][] = $rowNumbers;
122
					} else {
123
						$data[$this->params['group']][$groupedBy][] = $rowNumbers;
124
					}
125
				}
126
			}
127
		}
128
		return $data;
129
	}
130
131
	/**
132
	 * Data set sorting
133
	 *
134
	 * @since 1.8
135
	 *
136
	 * @param array $data label => value
137
	 *
138
	 * @return array
139
	 */
140
	private function getFormatSettings( $data, $options ) {
141
142
		// Init
143
		$dataSet = [];
144
		$options['mode'] = 'series';
145
		$options['autoscale'] = false;
146
147
		// Available markers
148
		$marker = [ 'circle', 'diamond', 'square', 'filledCircle', 'filledDiamond', 'filledSquare' ];
149
150
		// Series colour(has to be null otherwise jqplot runs with a type error)
151
		$seriescolors = $this->params['chartcolor'] !== '' ? array_filter(
152
			explode( ",", $this->params['chartcolor'] )
153
		) : null;
154
155
		// Re-grouping
156
		foreach ( $data[$this->params['group']] as $rowKey => $row ) {
157
			$values = [];
158
159
			foreach ( $row as $key => $value ) {
160
				// Switch labels according to the group parameter
161
				$label = $this->params['grouplabel'] === 'property' ? $value['property'] : $value['subject'];
162
				$values[] = [ $label, $value['value'] ];
163
			}
164
			$dataSet[] = $values;
165
		}
166
167
		// Series plotting parameters
168
		foreach ( $data[$this->params['group']] as $key => $row ) {
169
			$series[] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$series was never initialized. Although not strictly required by PHP, it is generally a good practice to add $series = 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...
170
				'label' => $key,
171
				'xaxis' => 'xaxis', // xaxis could also be xaxis2 or ...
172
				'yaxis' => 'yaxis',
173
				'fill' => $this->params['stackseries'],
174
				'showLine' => $this->params['charttype'] !== 'scatter',
175
				'showMarker' => true,
176
				'trendline' => [
177
					'show' => in_array( $this->params['trendline'], [ 'exp', 'linear' ] ),
178
					'shadow' => $this->params['theme'] !== 'simple',
179
					'type' => $this->params['trendline'],
180
				],
181
				'markerOptions' => [
182
					'style' => $marker[array_rand( $marker )],
183
					'shadow' => $this->params['theme'] !== 'simple'
184
				],
185
				'rendererOptions' => [ 'barDirection' => $this->params['direction'] ]
186
			];
187
		};
188
189
		// Basic parameters
190
		$parameters = [
191
			'numbersaxislabel' => $this->params['numbersaxislabel'],
192
			'labelaxislabel' => $this->params['labelaxislabel'],
193
			'charttitle' => $this->params['charttitle'],
194
			'charttext' => $this->params['charttext'],
195
			'infotext' => $this->params['infotext'],
196
			'theme' => $this->params['theme'] ? $this->params['theme'] : null,
197
			'valueformat' => $this->params['datalabels'] === 'label' ? '' : $this->params['valueformat'],
198
			'ticklabels' => $this->params['ticklabels'],
199
			'highlighter' => $this->params['highlighter'],
200
			'autoscale' => $options['autoscale'],
201
			'gridview' => $this->params['gridview'],
202
			'direction' => $this->params['direction'],
203
			'smoothlines' => $this->params['smoothlines'],
204
			'cursor' => $this->params['cursor'],
205
			'chartlegend' => $this->params['chartlegend'] !== '' ? $this->params['chartlegend'] : 'none',
206
			'colorscheme' => $this->params['colorscheme'] !== '' ? $this->params['colorscheme'] : null,
207
			'pointlabels' => $this->params['datalabels'] === 'none' ? false : $this->params['datalabels'],
208
			'datalabels' => $this->params['datalabels'],
209
			'stackseries' => $this->params['stackseries'],
210
			'grid' => $this->params['theme'] === 'vector' ? [ 'borderColor' => '#a7d7f9' ] : ( $this->params['theme'] === 'simple' ? [ 'borderColor' => '#ddd' ] : null ),
211
			'seriescolors' => $seriescolors,
212
			'hideZeroes' => $this->params['hidezeroes']
213
		];
214
215
		return [
216
			'data' => $dataSet,
217
			//'rawdata'      => $data , // control array
218
			'series' => $series,
0 ignored issues
show
Bug introduced by
The variable $series 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...
219
			'ticks' => $data['numbersticks'],
220
			'total' => $data['total'],
221
			'fcolumntypeid' => $data['fcolumntypeid'],
222
			'sask' => $options['sask'],
223
			'mode' => $options['mode'],
224
			'renderer' => $this->params['charttype'],
225
			'parameters' => $parameters
226
		];
227
	}
228
229
	/**
230
	 * Fetch numbers ticks
231
	 *
232
	 * @since 1.8
233
	 *
234
	 * @param array $data
235
	 */
236
	protected function getNumbersTicks( array $data ) {
237
238
		// Only look for numeric values that have been stored
239
		$numerics = array_values( $data['series'] );
240
241
		// Find min and max values to determine the graphs axis parameter
242
		$maxValue = count( $numerics ) == 0 ? 0 : max( array_map( "max", $numerics ) );
243
244
		if ( $this->params['min'] === false ) {
245
			$minValue = count( $numerics ) == 0 ? 0 : min( array_map( "min", $numerics ) );
246
		} else {
247
			$minValue = $this->params['min'];
248
		}
249
250
		// Get ticks info
251
		$data['numbersticks'] = SRFjqPlot::getNumbersTicks( $minValue, $maxValue );
252
		$data['total'] = array_sum( array_map( "array_sum", $numerics ) );
253
254
		return $data;
255
	}
256
257
	/**
258
	 * Add resource definitions
259
	 *
260
	 * @since 1.8
261
	 *
262
	 * @param array $data
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
263
	 *
264
	 * @return string
265
	 */
266
	protected function addResources() {
267
		// RL module
268
		switch ( $this->params['charttype'] ) {
269
			case 'bubble':
270
				SMWOutputs::requireResource( 'ext.srf.jqplot.bubble' );
271
				break;
272
			case 'donut':
273
				SMWOutputs::requireResource( 'ext.srf.jqplot.donut' );
274
				break;
275
			case 'scatter':
276
			case 'line':
277
			case 'bar':
278
				SMWOutputs::requireResource( 'ext.srf.jqplot.bar' );
279
				break;
280
		}
281
282
		// Trendline plugin
283
		if ( in_array( $this->params['trendline'], [ 'exp', 'linear' ] ) ) {
284
			SMWOutputs::requireResource( 'ext.srf.jqplot.trendline' );
285
		}
286
287
		// Cursor plugin
288
		if ( in_array( $this->params['cursor'], [ 'zoom', 'tooltip' ] ) ) {
289
			SMWOutputs::requireResource( 'ext.srf.jqplot.cursor' );
290
		}
291
292
		// Highlighter plugin
293
		if ( $this->params['highlighter'] ) {
294
			SMWOutputs::requireResource( 'ext.srf.jqplot.highlighter' );
295
		}
296
297
		// Enhancedlegend plugin
298
		if ( $this->params['chartlegend'] ) {
299
			SMWOutputs::requireResource( 'ext.srf.jqplot.enhancedlegend' );
300
		}
301
302
		// gridview plugin
303
		if ( in_array( $this->params['gridview'], [ 'tabs' ] ) ) {
304
			SMWOutputs::requireResource( 'ext.srf.util.grid' );
305
		}
306
307
		// Pointlabels plugin
308
		if ( in_array( $this->params['datalabels'], [ 'value', 'label', 'percent' ] ) ) {
309
			SMWOutputs::requireResource( 'ext.srf.jqplot.pointlabels' );
310
		}
311
	}
312
313
	/**
314
	 * Prepare data for the output
315
	 *
316
	 * @since 1.8
317
	 *
318
	 * @param array $data
319
	 *
320
	 * @return string
321
	 */
322
	protected function getFormatOutput( array $data ) {
323
324
		$this->isHTML = true;
325
326
		static $statNr = 0;
327
		$chartID = 'jqplot-series-' . ++$statNr;
328
329
		// Encoding
330
		$requireHeadItem = [ $chartID => FormatJson::encode( $data ) ];
331
		SMWOutputs::requireHeadItem( $chartID, Skin::makeVariablesScript( $requireHeadItem ) );
332
333
		// Add RL resources
334
		$this->addResources();
335
336
		// Processing placeholder
337
		$processing = SRFUtils::htmlProcessingElement( $this->isHTML );
338
339
		// Conversion due to a string as value that can contain %
340
		$width = strstr( $this->params['width'], "%" ) ? $this->params['width'] : $this->params['width'] . 'px';
341
342
		// Chart/graph placeholder
343
		$chart = Html::rawElement(
344
			'div',
345
			[
346
				'id' => $chartID,
347
				'class' => 'container',
348
				'style' => "display:none; width: {$width}; height: {$this->params['height']}px;"
349
			],
350
			null
351
		);
352
353
		// Beautify class selector
354
		$class = $this->params['charttype'] ? '-' . $this->params['charttype'] : '';
355
		$class = $this->params['class'] ? $class . ' ' . $this->params['class'] : $class . ' jqplot-common';
356
357
		// Chart/graph wrappper
358
		return Html::rawElement(
359
			'div',
360
			[
361
				'class' => 'srf-jqplot' . $class,
362
			],
363
			$processing . $chart
364
		);
365
	}
366
367
	/**
368
	 * @see SMWResultPrinter::getParamDefinitions
369
	 *
370
	 * @since 1.8
371
	 *
372
	 * @param $definitions array of IParamDefinition
373
	 *
374
	 * @return array of IParamDefinition|array
375
	 */
376
	public function getParamDefinitions( array $definitions ) {
377
		$params = array_merge( parent::getParamDefinitions( $definitions ), SRFjqPlot::getCommonParams() );
378
379
		$params['infotext'] = [
380
			'message' => 'srf-paramdesc-infotext',
381
			'default' => '',
382
		];
383
384
		$params['stackseries'] = [
385
			'type' => 'boolean',
386
			'message' => 'srf-paramdesc-stackseries',
387
			'default' => false,
388
		];
389
390
		$params['group'] = [
391
			'message' => 'srf-paramdesc-group',
392
			'default' => 'subject',
393
			'values' => [ 'property', 'subject' ],
394
		];
395
396
		$params['grouplabel'] = [
397
			'message' => 'srf-paramdesc-grouplabel',
398
			'default' => 'subject',
399
			'values' => [ 'property', 'subject' ],
400
		];
401
402
		$params['charttype'] = [
403
			'message' => 'srf-paramdesc-charttype',
404
			'default' => 'bar',
405
			'values' => [ 'bar', 'line', 'donut', 'bubble', 'scatter' ],
406
		];
407
408
		$params['trendline'] = [
409
			'message' => 'srf-paramdesc-trendline',
410
			'default' => 'none',
411
			'values' => [ 'none', 'exp', 'linear' ],
412
		];
413
414
		$params['cursor'] = [
415
			'message' => 'srf-paramdesc-chartcursor',
416
			'default' => 'none',
417
			'values' => [ 'none', 'zoom', 'tooltip' ],
418
		];
419
420
		$params['gridview'] = [
421
			'message' => 'srf-paramdesc-gridview',
422
			'default' => 'none',
423
			'values' => [ 'none', 'tabs' ],
424
		];
425
426
		$params['hidezeroes'] = [
427
			'type' => 'boolean',
428
			'message' => 'srf-paramdesc-hidezeroes',
429
			'default' => false,
430
		];
431
432
		return $params;
433
	}
434
}
435