Completed
Pull Request — master (#467)
by
unknown
19:49
created

GanttPrinter   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 6
dl 0
loc 282
rs 9.2
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 4 1
B getParamDefinitions() 0 65 1
C handleParameters() 0 77 14
F getResultText() 0 115 24

How to fix   Complexity   

Complex Class

Complex classes like GanttPrinter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GanttPrinter, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * SMW result printer for Gantt Diagrams using mermaidjs.
4
 * https://github.com/knsv/mermaid
5
 *
6
 * In order to use this printer you need to have
7
 * the Mermaid MediaWiki extension installed.
8
 * https://www.mediawiki.org/wiki/Extension:Mermaid
9
 *
10
 * @file Gantt.php
11
 * @ingroup SemanticResultFormats
12
 *
13
 * @licence GNU GPL v2+
14
 * @author Sebastian Schmid
15
 */
16
17
namespace SRF\Gantt;
18
19
use SMWOutputs;
20
use SMWQueryResult;
21
use SMWResultPrinter;
22
use SMWDITime;
23
use SMWDIBlob;
24
use Html;
25
26
class GanttPrinter extends SMWResultPrinter {
27
28
	protected $mParams = [];
29
	protected $mGantt = null;
30
	protected $mErrors = [];
31
32
	public function getName() {
33
		// Give grep a chance to find the usage
34
		return wfMessage( 'srf-printername-gantt' )->text();
35
	}
36
37
	public function getParamDefinitions( array $definitions ) {
38
		$params = parent::getParamDefinitions( $definitions );
39
40
		$params[] = [
41
			'name'    => 'diagramtitle',
42
			'message' => 'srf-paramdesc-gantt-diagramtitle',
43
			'default' => ''
44
		];
45
46
		$params[] = [
47
			'name'    => 'theme',
48
			'message' => 'srf-paramdesc-gantt-diagramtheme',
49
			'default' => 'default'
50
		];
51
52
		$params[] = [
53
			'name'    => 'axisformat',
54
			'message' => 'srf-paramdesc-gantt-axisformat',
55
			'default' => '%m/%d/%Y'
56
		];
57
58
		$params[] = [
59
			'name'    => 'sortkey',
60
			'message' => 'srf-paramdesc-gantt-sortkey',
61
			'default' => 'startdate'
62
		];
63
64
		$params[] = [
65
			'name'    => 'statusmapping',
66
			'message' => 'srf-paramdesc-gantt-statusmapping',
67
			'default' => ''
68
		];
69
70
		$params[] = [
71
			'name'    => 'prioritymapping',
72
			'message' => 'srf-paramdesc-gantt-prioritymapping',
73
			'default' => ''
74
		];
75
76
		$params[] = [
77
			'name'    => 'titletopmargin',
78
			'message' => 'srf-paramdesc-gantt-titletopmargin',
79
			'default' => 25
80
		];
81
82
		$params[] = [
83
			'name'    => 'barheight',
84
			'message' => 'srf-paramdesc-gantt-barheight',
85
			'default' => 20
86
		];
87
88
		$params[] = [
89
			'name'    => 'leftpadding',
90
			'message' => 'srf-paramdesc-gantt-leftpadding',
91
			'default' => 75
92
		];
93
94
		$params[] = [
95
			'name'    => 'bargap',
96
			'message' => 'srf-paramdesc-gantt-bargap',
97
			'default' => 4
98
		];
99
100
		return $params;
101
	}
102
103
	/**
104
	 * Handle (set) the result format parameters
105
	 *
106
	 * @see SMWResultPrinter::handleParameters()
107
	 */
108
	protected function handleParameters( array $params, $outputmode ) {
109
110
		parent::handleParameters( $params, $outputmode );
111
112
		//Set header params
113
		$this->mParams['title'] = trim( $params['diagramtitle'] );
114
		$this->mParams['axisformat'] = trim( $params['axisformat'] );
115
		$this->mParams['sortkey'] = trim( $params['sortkey'] );
116
		$this->mParams['statusmapping'] = trim( $params['statusmapping'] );
117
		$this->mParams['prioritymapping'] = trim( $params['prioritymapping'] );
118
		$this->mParams['theme'] = trim( $params['theme'] );
119
120
		//Validate Theme
121
		if ( !in_array( $this->params['theme'], [ "default", "neutral", "dark", "forest" ] ) ) {
122
			array_push( $this->mErrors, wfMessage( 'srf-error-gantt-theme' )->text() );
123
		}
124
125
		//Validate sortkey
126
		if ( !in_array( strtolower( $this->params['sortkey'] ), [ "title", "startdate", "enddate" ] ) ) {
127
			array_push( $this->mErrors, wfMessage( 'srf-error-gantt-sortkey' )->text() );
128
		}
129
130
		//Validate mapping
131
		if ( !empty( trim( $params['statusmapping'] ) ) ) {
132
133
			$paramMapping = explode( ';', trim( $params['statusmapping'] ) );
134
135
			foreach ( $paramMapping as $pm ) {
136
137
				// if no "=>" pattern was found
138
				if ( !strpos( $pm, '=>' ) ) {
139
					array_push( $this->mErrors, wfMessage( 'srf-error-gantt-mapping-assignment', 'statusmapping' )->text() );
140
				} else {
141
					$pmKeyVal = explode( '=>', $pm );
142
					// if no key value pair
143
					if ( count( $pmKeyVal ) % 2 != 0 ) {
144
						array_push( $this->mErrors,
145
							wfMessage( 'srf-error-gantt-mapping-assignment', 'statusmapping' )->text() );
146
					} else {
147
						$mapping[trim( $pmKeyVal[0] )] = trim( $pmKeyVal[1] );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$mapping was never initialized. Although not strictly required by PHP, it is generally a good practice to add $mapping = 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...
148
						// check if the common status keys are used
149
						if ( trim( $pmKeyVal[1] ) != "active" && trim( $pmKeyVal[1] ) != "done" ) {
150
							array_push( $this->mErrors, wfMessage( 'srf-error-gantt-mapping-keywords' )->text() );
151
						}
152
					}
153
				}
154
			}
155
		}
156
		if ( !empty( trim( $params['prioritymapping'] ) ) ) {
157
158
			$paramMapping = explode( ';', trim( $params['prioritymapping'] ) );
159
160
			foreach ( $paramMapping as $pm ) {
161
162
				// if no "=>" pattern was found
163
				if ( !strpos( $pm, '=>' ) ) {
164
					array_push( $this->mErrors,
165
						wfMessage( 'srf-error-gantt-mapping-assignment', 'prioritymapping' )->text() );
166
				} else {
167
					$pmKeyVal = explode( '=>', $pm );
168
					// if no key value pair
169
					if ( count( $pmKeyVal ) % 2 != 0 ) {
170
						array_push( $this->mErrors,
171
							wfMessage( 'srf-error-gantt-mapping-assignment', 'statusmapping' )->text() );
172
					} else {
173
						$mapping[trim( $pmKeyVal[0] )] = trim( $pmKeyVal[1] );
0 ignored issues
show
Bug introduced by
The variable $mapping 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...
174
						// check if the common status keys are used
175
						if ( trim( $pmKeyVal[1] ) != "crit" ) {
176
							array_push( $this->mErrors, wfMessage( 'srf-error-gantt-mapping-keywords' )->text() );
177
						}
178
					}
179
				}
180
			}
181
		}
182
183
		$this->mGantt = new Gantt( $this->mParams );
184
	}
185
186
	/**
187
	 * Return serialised results in specified format.
188
	 * @param SMWQueryResult $queryResult
189
	 * @param $outputmode
190
	 * @return string
191
	 */
192
	protected function getResultText( SMWQueryResult $queryResult, $outputmode ) {
193
194
		// Show warning if Extension:Mermaid is not available
195
		if ( !class_exists( 'Mermaid' ) && !class_exists( 'Mermaid\\MermaidParserFunction' ) ) {
196
			//wfWarn( 'The SRF Mermaid format needs the Mermaid extension to be installed.' );
197
				$queryResult->addErrors( ["Error: Mermaid Extension needs to be installed."] );
198
			return '';
199
		}
200
201
		// Load general Modules
202
		// First load the dependent modules of Mermaid ext
203
		SMWOutputs::requireResource( 'ext.mermaid' );
204
		SMWOutputs::requireResource( 'ext.mermaid.styles' );
205
		SMWOutputs::requireResource( 'ext.srf.gantt' );
206
207
		//Add Tasks & Sections
208
		while ( $row = $queryResult->getNext() ) {
209
210
			$status = [];
211
			$priority = [];
212
			$startDate = "";
213
			$endDate = "";
214
			$taskID = "";
215
			$taskTitle = "";
216
			$sections = [];
217
218
			// Loop through all field of a row
219
			foreach ( $row as $field ) {
220
221
				$fieldLabel = $field->getPrintRequest()->getLabel();
222
223
				//get values
224
				foreach ( $field->getContent() as $dataItem ) {
225
226
					switch ( $fieldLabel ) {
227
						case "section":
228
							$sections[$dataItem->getTitle()->getPrefixedDBKey()] = $dataItem->getSortKey();
229
							break;
230
						case "task":
231
							if ( $dataItem instanceof SMWDIBlob ) {
232
								$taskTitle = $dataItem->getString();
233
								$taskID = $field->getResultSubject()->getTitle()->getPrefixedDBKey();
234
							}
235
							break;
236
						case "startdate":
237
							if ( $dataItem instanceof SMWDITime ) {
238
								$startDate = $dataItem->getMwTimestamp();
239
							}
240
							break;
241
						case "enddate":
242
							if ( $dataItem instanceof SMWDITime ) {
243
								$endDate = $dataItem->getMwTimestamp();
244
							}
245
							break;
246
						case "status":
247
							if ( $dataItem instanceof SMWDIBlob ) {
248
								array_push( $status, $dataItem->getString() );
249
							}
250
							break;
251
						case "priority":
252
							if ( $dataItem instanceof SMWDIBlob ) {
253
								array_push( $priority, $dataItem->getString() );
254
							}
255
							break;
256
					}
257
				}
258
			}
259
260
			// Add section/Task
261
			// Title, TaskID, StartDate and EndDate are required
262
			if ( $taskID != "" && $taskTitle != "" && $startDate != "" && $endDate != "" ) {
263
				$this->mGantt->addTask( $taskID, $taskTitle, $status, $priority, $startDate, $endDate );
264
265
				// If no section was found, put task into a dummy section object
266
				// "gantt-no-section#21780240" is used to identify Tasks that with no section (dummy section)
267
				if ( count( $sections ) == 0 ) {
268
					$this->mGantt->addSection( "gantt-no-section#21780240", "", $startDate, $endDate, $taskID );
269
				} else {
270
					foreach ( $sections as $sectionID => $sectionTitle ) {
271
						$this->mGantt->addSection( $sectionID, $sectionTitle, $startDate, $endDate, $taskID );
272
					}
273
				}
274
			}
275
		}
276
277
		// Improve unique id by adding a random number
278
		$id = uniqid( 'srf-gantt-' . rand( 1, 10000 ) );
279
280
		// Add gantt configurations
281
		$config = [
282
			'theme' => $this->params['theme'],
283
			'gantt' => [
284
				'leftPadding'    => intval( $this->params['leftpadding'] ),
285
				'titleTopMargin' => intval( $this->params['titletopmargin'] ),
286
				'barHeight'      => intval( $this->params['barheight'] ),
287
				'barGap'         => intval( $this->params['bargap'] )
288
			]
289
		];
290
291
		// Manage Output
292
		if ( !empty( $this->mErrors ) ) {
293
			return $queryResult->addErrors( $this->mErrors );
294
		} else {
295
			return Html::rawElement( 'div', [
296
				'id'           => $id,
297
				'class'        => 'srf-gantt',
298
				'data-mermaid' => json_encode( [
299
					'content' => $this->mGantt->getGanttOutput(),
300
					'config'  => $config
301
				], JSON_UNESCAPED_UNICODE )
302
			], Html::rawElement( 'div', [
303
				'class' => 'mermaid-dots',
304
			] ) );
305
		}
306
	}
307
}