TagCloud::getTagSizes()   F
last analyzed

Complexity

Conditions 22
Paths 673

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 82.5

Importance

Changes 0
Metric Value
dl 0
loc 85
ccs 27
cts 54
cp 0.5
rs 0.4541
c 0
b 0
f 0
cc 22
nc 673
nop 1
crap 82.5

How to fix   Long Method    Complexity   

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
namespace SRF;
4
5
use Html;
6
use SMW\ResultPrinter;
7
use SMWDataValue;
8
use SMWOutputs;
9
use SMWPrintRequest;
10
use SMWQueryResult;
11
use SMWResultArray;
12
use SRFUtils;
13
use Title;
14
15
/**
16
 * Result printer that prints query results as a tag cloud
17
 *
18
 * @since 1.5.3
19
 *
20
 * @ingroup SRF
21
 * @ingroup QueryPrinter
22
 *
23
 * @licence GNU GPL v2 or later
24
 * @author Jeroen De Dauw < [email protected] >
25
 * @author mwjames
26
 */
27
class TagCloud extends ResultPrinter {
28
29
	/**
30
	 * Contains html generated tags
31
	 *
32
	 * @var array
33
	 */
34
	protected $tagsHtml = [];
35
36
	/**
37
	 * Get a human readable label for this printer.
38
	 *
39
	 * @return string
40
	 */
41
	public function getName() {
42
		return $this->msg( 'srf_printername_tagcloud' )->text();
43
	}
44
45
	/**
46
	 * Return serialised results in specified format
47
	 *
48
	 * @param SMWQueryResult $queryResult
49
	 * @param $outputmode
50
	 *
51
	 * @return string
52
	 */
53 2
	public function getResultText( SMWQueryResult $queryResult, $outputmode ) {
54
55 2
		$tags = $this->getTags( $queryResult, $outputmode );
56
57 2
		if ( $tags === [] ) {
58
			$queryResult->addErrors( [ $this->msg( 'smw_result_noresults' )->inContentLanguage()->text() ] );
59
			return '';
60
		}
61
62
		// Check output conditions
63 2
		if ( ( $this->params['widget'] == 'sphere' ) &&
64 2
			( $this->params['link'] !== 'all' ) &&
65 2
			( $this->params['template'] === '' ) ) {
66
			$queryResult->addErrors(
67
				[ $this->msg( 'srf-error-option-link-all', 'sphere' )->inContentLanguage()->text() ]
68
			);
69
			return '';
70
		}
71
72
		// Template support
73 2
		$this->hasTemplates = $this->params['template'] !== '';
74 2
		$this->isHTML = $this->isHTML();
75
76
		// Register RL module
77 2
		if ( in_array( $this->params['widget'], [ 'sphere', 'wordcloud' ] ) ) {
78
			SMWOutputs::requireResource( 'ext.srf.formats.tagcloud' );
79
		}
80
81 2
		return $this->getTagCloud( $this->getTagSizes( $tags ) );
82
	}
83
84 2
	private function isHTML() {
85 2
		$title = $GLOBALS['wgTitle'];
86
87 2
		if ( $title instanceof Title ) {
0 ignored issues
show
Bug introduced by mwjames
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
88
			return $title->isSpecialPage() && !$this->hasTemplates;
89
		}
90
91 2
		return false;
92
	}
93
94
	/**
95
	 * Returns an array with the tags (keys) and the number of times they occur (values).
96
	 *
97
	 * @param SMWQueryResult $queryResult
98
	 * @param $outputMode
99
	 *
100
	 * @return array
101
	 */
102 2
	private function getTags( SMWQueryResult $queryResult, $outputMode ) {
103 2
		$tags = [];
104 2
		$excludetags = explode( ';', $this->params['excludetags'] );
105
106
		/**
107
		 * @var SMWResultArray $row
108
		 * @var SMWDataValue $dataValue
109
		 */
110 2
		while ( $row = $queryResult->getNext() ) { // Objects (pages)
111 2
			for ( $i = 0, $n = count( $row ); $i < $n; $i++ ) { // SMWResultArray for a sinlge property
112
113 2
				while ( ( $dataValue = $row[$i]->getNextDataValue() ) !== false ) { // Data values
114
115 2
					$isSubject = $row[$i]->getPrintRequest()->getMode() == SMWPrintRequest::PRINT_THIS;
116
117
					// If the main object should not be included, skip it.
118 2
					if ( $i == 0 && !$this->params['includesubject'] && $isSubject ) {
119 2
						continue;
120
					}
121
122 2
					$value = null;
123
124
					// Get the HTML for the tag content. Pages are linked, other stuff is just plaintext.
125 2
					if ( $dataValue->getTypeID() === '_wpg' ) {
126
127 1
						$title = $dataValue->getDataItem()->getTitle();
128
129 1
						if ( $title instanceof Title ) {
0 ignored issues
show
Bug introduced by Stephan
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
130 1
							$value = $title->getPrefixedText();
131 1
							$html = $dataValue->getLongText( $outputMode, $this->getLinker( $isSubject ) );
132
						}
133
					}
134
135 2
					if ( $value === null ) {
136 1
						$html = $dataValue->getShortText( $outputMode, $this->getLinker( false ) );
137 1
						$value = $html;
138
					}
139
140
					// Exclude tags from result set
141 2
					if ( in_array( $value, $excludetags ) ) {
142
						continue;
143
					}
144
145
					// Replace content with template inclusion
146 2
					$html = $this->params['template'] !== '' ? $this->addTemplateOutput( $value, $rownum ) : $html;
0 ignored issues
show
Bug introduced by Jeroen De Dauw
The variable $html 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...
147
148
					// Store the HTML separately, so sorting can be done easily
149 2
					if ( !array_key_exists( $value, $tags ) ) {
150 2
						$tags[$value] = 0;
151 2
						$this->tagsHtml[$value] = $html;
152
					}
153
154 2
					$tags[$value]++;
155
				}
156
			}
157
		}
158
159 2
		foreach ( $tags as $name => $count ) {
160 2
			if ( $count < $this->params['mincount'] ) {
161
				unset( $tags[$name] );
162
			}
163
		}
164
165 2
		return $tags;
166
	}
167
168
	/**
169
	 * Determines the sizes of tags.
170
	 * This method is based on code from the FolkTagCloud extension by Katharina Wäschle.
171
	 *
172
	 * @param array $tags
173
	 *
174
	 * @return array
175
	 */
176 2
	private function getTagSizes( array $tags ) {
177 2
		if ( count( $tags ) == 0 ) {
178
			return $tags;
179
		}
180
181
		// If the original order needs to be kept, we need a copy of the current order.
182 2
		if ( $this->params['tagorder'] == 'unchanged' ) {
183
			$unchangedTags = array_keys( $tags );
184
		}
185
186 2
		arsort( $tags, SORT_NUMERIC );
187
188 2
		if ( count( $tags ) > $this->params['maxtags'] ) {
189
			$tags = array_slice( $tags, 0, $this->params['maxtags'], true );
190
		}
191
192 2
		$min = end( $tags ) or $min = 0;
193 2
		$max = reset( $tags ) or $max = 1;
194 2
		$maxSizeIncrease = $this->params['maxsize'] - $this->params['minsize'];
195
196
		// Loop over the tags, and replace their count by a size.
197 2
		foreach ( $tags as &$tag ) {
198 2
			switch ( $this->params['increase'] ) {
199 2
				case 'linear':
200
					$divisor = ( $max == $min ) ? 1 : $max - $min;
201
					$tag = $this->params['minsize'] + $maxSizeIncrease * ( $tag - $min ) / $divisor;
202
					break;
203 2
				case 'log' :
204
				default :
205 2
					$divisor = ( $max == $min ) ? 1 : log( $max ) - log( $min );
206 2
					$tag = $this->params['minsize'] + $maxSizeIncrease * ( log( $tag ) - log( $min ) ) / $divisor;
207 2
					break;
208
			}
209
		}
210
211 2
		switch ( $this->params['tagorder'] ) {
212 2
			case 'desc' :
213
				// Tags are already sorted desc
214
				break;
215 2
			case 'asc' :
216
				asort( $tags );
217
				break;
218 2
			case 'alphabetical' :
219 2
				$tagNames = array_keys( $tags );
220 2
				natcasesort( $tagNames );
221 2
				$newTags = [];
222
223 2
				foreach ( $tagNames as $name ) {
224 2
					$newTags[$name] = $tags[$name];
225
				}
226
227 2
				$tags = $newTags;
228 2
				break;
229
			case 'random' :
230
				$tagSizes = $tags;
231
				shuffle( $tagSizes );
232
				$newTags = [];
233
234
				foreach ( $tagSizes as $size ) {
235
					foreach ( $tags as $tagName => $tagSize ) {
236
						if ( $tagSize == $size ) {
237
							$newTags[$tagName] = $tags[$tagName];
238
							break;
239
						}
240
					}
241
				}
242
243
				$tags = $newTags;
244
				break;
245
			case 'unchanged' :
246
			default : // Restore the original order.
247
				$changedTags = $tags;
248
				$tags = [];
249
250
				foreach ( $unchangedTags as $name ) {
0 ignored issues
show
Bug introduced by Jeroen De Dauw
The variable $unchangedTags 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...
251
					// Original tags might have been left out at this point, so only add remaining ones.
252
					if ( array_key_exists( $name, $changedTags ) ) {
253
						$tags[$name] = $changedTags[$name];
254
					}
255
				}
256
				break;
257
		}
258
259 2
		return $tags;
260
	}
261
262
	/**
263
	 * Returns the HTML for the tag cloud.
264
	 *
265
	 * @param array $tags
266
	 *
267
	 * @return string
268
	 */
269 2
	private function getTagCloud( array $tags ) {
270
271
		// Initialize
272 2
		$htmlTags = [];
273 2
		$processing = '';
274
275
		// Count actual output and store div identifier
276 2
		$tagId = 'srf-' . uniqid();
277
278
		// Determine HTML element marker
279 2
		$element = $this->params['widget'] !== '' ? 'li' : 'span';
280
281
		// Add size information
282 2
		foreach ( $tags as $name => $size ) {
283 2
			$htmlTags[] = Html::rawElement(
284 2
				$element,
285
				[
286 2
					'style' => "font-size:$size%" ],
287 2
				$this->tagsHtml[$name]
288
			);
289
		}
290
291
		// Stringify
292 2
		$htmlSTags = implode( ' ', $htmlTags );
293
294
		// Handle sphere/canvas output objects
295 2
		if ( in_array( $this->params['widget'], [ 'sphere', 'wordcloud' ] ) ) {
296
297
			// Wrap LI/UL elements
298
			$htmlCTags = Html::rawElement(
299
				'ul',
300
				[
301
					'style' => 'display:none;'
302
				],
303
				$htmlSTags
304
			);
305
306
			// Wrap tags
307
			$htmlCTags = Html::rawElement(
308
				'div',
309
				[
310
					'id' => $tagId . '-tags',
311
					'class' => 'srf-tags'
312
				],
313
				$htmlCTags
314
			);
315
316
			// Wrap everything in a container object
317
			$htmlSTags = Html::rawElement(
318
				'div',
319
				[
320
					'id' => $tagId . '-container',
321
					'class' => 'srf-container',
322
					'data-width' => $this->params['width'],
323
					'data-height' => $this->params['height'],
324
					'data-font' => $this->params['font']
325
				],
326
				$htmlCTags
327
			);
328
329
			// Processing placeholder
330
			$processing = SRFUtils::htmlProcessingElement();
331
		}
332
333
		// Beautify class selector
334 2
		$class = $this->params['widget'] ? '-' . $this->params['widget'] . ' ' : '';
335 2
		$class = $this->params['class'] ? $class . ' ' . $this->params['class'] : $class;
336
337
		// General placeholder
338
		$attribs = [
339 2
			'class' => 'srf-tagcloud' . $class,
340 2
			'data-version' => '0.4.1'
341
		];
342
343 2
		return Html::rawElement( 'div', $attribs, $processing . $htmlSTags );
344
	}
345
346
	/**
347
	 * @param string $value
348
	 * @param int $rowNumber
349
	 *
350
	 * @return string
351
	 */
352 2
	private function addTemplateOutput( $value, &$rowNumber ) {
353 2
		$rowNumber++;
354 2
		$wikitext = $this->params['userparam'] ? "|userparam=" . $this->params['userparam'] : '';
355 2
		$wikitext .= "|" . $value;
356 2
		$wikitext .= "|#=$rowNumber";
357 2
		return '{{' . trim( $this->params['template'] ) . $wikitext . '}}';
358
	}
359
360
	/**
361
	 * @see ResultPrinter::getParamDefinitions
362
	 *
363
	 * @since 1.8
364
	 *
365
	 * @param $definitions array of IParamDefinition
366
	 *
367
	 * @return array of IParamDefinition|array
368
	 */
369 2
	public function getParamDefinitions( array $definitions ) {
370 2
		$params = parent::getParamDefinitions( $definitions );
371
372 2
		$params['template'] = [
373
			'message' => 'smw-paramdesc-template',
374
			'default' => '',
375
		];
376
377 2
		$params['userparam'] = [
378
			'message' => 'smw-paramdesc-userparam',
379
			'default' => '',
380
		];
381
382 2
		$params['excludetags'] = [
383
			'message' => 'srf-paramdesc-excludetags',
384
			'default' => '',
385
		];
386
387 2
		$params['includesubject'] = [
388
			'type' => 'boolean',
389
			'message' => 'srf-paramdesc-includesubject',
390
			'default' => false,
391
		];
392
393 2
		$params['tagorder'] = [
394
			'message' => 'srf_paramdesc_tagorder',
395
			'default' => 'alphabetical',
396
			'values' => [ 'alphabetical', 'asc', 'desc', 'random', 'unchanged' ],
397
		];
398
399 2
		$params['increase'] = [
400
			'message' => 'srf_paramdesc_increase',
401
			'default' => 'log',
402
			'values' => [ 'linear', 'log' ],
403
		];
404
405 2
		$params['widget'] = [
406
			'message' => 'srf-paramdesc-widget',
407
			'default' => '',
408
			'values' => [ 'sphere', 'wordcloud' ],
409
		];
410
411 2
		$params['class'] = [
412
			'message' => 'smw-paramdesc-class',
413
			'default' => '',
414
		];
415
416 2
		$params['font'] = [
417
			'message' => 'srf-paramdesc-font',
418
			'default' => 'impact',
419
		];
420
421 2
		$params['height'] = [
422
			'type' => 'integer',
423
			'message' => 'srf-paramdesc-height',
424
			'default' => 400,
425
			'lowerbound' => 1,
426
		];
427
428 2
		$params['width'] = [
429
			'type' => 'integer',
430
			'message' => 'srf-paramdesc-width',
431
			'default' => 400,
432
			'lowerbound' => 1,
433
		];
434
435 2
		$params['mincount'] = [
436
			'type' => 'integer',
437
			'message' => 'srf_paramdesc_mincount',
438
			'default' => 1,
439
			'manipulatedefault' => false,
440
		];
441
442 2
		$params['minsize'] = [
443
			'type' => 'integer',
444
			'message' => 'srf_paramdesc_minsize',
445
			'default' => 77,
446
			'manipulatedefault' => false,
447
		];
448
449 2
		$params['maxsize'] = [
450
			'type' => 'integer',
451
			'message' => 'srf_paramdesc_maxsize',
452
			'default' => 242,
453
			'manipulatedefault' => false,
454
		];
455
456 2
		$params['maxtags'] = [
457
			'type' => 'integer',
458
			'message' => 'srf_paramdesc_maxtags',
459
			'default' => 1000,
460
			'lowerbound' => 1,
461
		];
462
463 2
		return $params;
464
	}
465
}
466