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

formats/tagcloud/TagCloud.php (1 issue)

a variable is defined regardless of execution path.

Bug Major

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
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 ) {
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
					// Get the HTML for the tag content. Pages are linked, other stuff is just plaintext.
123 2
					if ( $dataValue->getTypeID() === '_wpg' && $dataValue->getTitle() instanceof Title ) {
124 1
						$value = $dataValue->getTitle()->getPrefixedText();
125 1
						$html = $dataValue->getLongText( $outputMode, $this->getLinker( $isSubject ) );
126
					} else {
127 1
						$html = $dataValue->getShortText( $outputMode, $this->getLinker( false ) );
128 1
						$value = $html;
129
					}
130
131
					// Exclude tags from result set
132 2
					if ( in_array( $value, $excludetags ) ) {
133
						continue;
134
					}
135
136
					// Replace content with template inclusion
137 2
					$html = $this->params['template'] !== '' ? $this->addTemplateOutput( $value, $rownum ) : $html;
138
139
					// Store the HTML separately, so sorting can be done easily
140 2
					if ( !array_key_exists( $value, $tags ) ) {
141 2
						$tags[$value] = 0;
142 2
						$this->tagsHtml[$value] = $html;
143
					}
144
145 2
					$tags[$value]++;
146
				}
147
			}
148
		}
149
150 2
		foreach ( $tags as $name => $count ) {
151 2
			if ( $count < $this->params['mincount'] ) {
152 2
				unset( $tags[$name] );
153
			}
154
		}
155
156 2
		return $tags;
157
	}
158
159
	/**
160
	 * Determines the sizes of tags.
161
	 * This method is based on code from the FolkTagCloud extension by Katharina Wäschle.
162
	 *
163
	 * @param array $tags
164
	 *
165
	 * @return array
166
	 */
167 2
	private function getTagSizes( array $tags ) {
168 2
		if ( count( $tags ) == 0 ) {
169
			return $tags;
170
		}
171
172
		// If the original order needs to be kept, we need a copy of the current order.
173 2
		if ( $this->params['tagorder'] == 'unchanged' ) {
174
			$unchangedTags = array_keys( $tags );
175
		}
176
177 2
		arsort( $tags, SORT_NUMERIC );
178
179 2
		if ( count( $tags ) > $this->params['maxtags'] ) {
180
			$tags = array_slice( $tags, 0, $this->params['maxtags'], true );
181
		}
182
183 2
		$min = end( $tags ) or $min = 0;
184 2
		$max = reset( $tags ) or $max = 1;
185 2
		$maxSizeIncrease = $this->params['maxsize'] - $this->params['minsize'];
186
187
		// Loop over the tags, and replace their count by a size.
188 2
		foreach ( $tags as &$tag ) {
189 2
			switch ( $this->params['increase'] ) {
190 2
				case 'linear':
191
					$divisor = ( $max == $min ) ? 1 : $max - $min;
192
					$tag = $this->params['minsize'] + $maxSizeIncrease * ( $tag - $min ) / $divisor;
193
					break;
194 2
				case 'log' :
195
				default :
196 2
					$divisor = ( $max == $min ) ? 1 : log( $max ) - log( $min );
197 2
					$tag = $this->params['minsize'] + $maxSizeIncrease * ( log( $tag ) - log( $min ) ) / $divisor;
198 2
					break;
199
			}
200
		}
201
202 2
		switch ( $this->params['tagorder'] ) {
203 2
			case 'desc' :
204
				// Tags are already sorted desc
205
				break;
206 2
			case 'asc' :
207
				asort( $tags );
208
				break;
209 2
			case 'alphabetical' :
210 2
				$tagNames = array_keys( $tags );
211 2
				natcasesort( $tagNames );
212 2
				$newTags = [];
213
214 2
				foreach ( $tagNames as $name ) {
215 2
					$newTags[$name] = $tags[$name];
216
				}
217
218 2
				$tags = $newTags;
219 2
				break;
220
			case 'random' :
221
				$tagSizes = $tags;
222
				shuffle( $tagSizes );
223
				$newTags = [];
224
225
				foreach ( $tagSizes as $size ) {
226
					foreach ( $tags as $tagName => $tagSize ) {
227
						if ( $tagSize == $size ) {
228
							$newTags[$tagName] = $tags[$tagName];
229
							break;
230
						}
231
					}
232
				}
233
234
				$tags = $newTags;
235
				break;
236
			case 'unchanged' :
237
			default : // Restore the original order.
238
				$changedTags = $tags;
239
				$tags = [];
240
241
				foreach ( $unchangedTags as $name ) {
0 ignored issues
show
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...
242
					// Original tags might have been left out at this point, so only add remaining ones.
243
					if ( array_key_exists( $name, $changedTags ) ) {
244
						$tags[$name] = $changedTags[$name];
245
					}
246
				}
247
				break;
248
		}
249
250 2
		return $tags;
251
	}
252
253
	/**
254
	 * Returns the HTML for the tag cloud.
255
	 *
256
	 * @param array $tags
257
	 *
258
	 * @return string
259
	 */
260 2
	private function getTagCloud( array $tags ) {
261
262
		// Initialize
263 2
		$htmlTags = [];
264 2
		$processing = '';
265
266
		// Count actual output and store div identifier
267 2
		$tagId = 'srf-' . uniqid();
268
269
		// Determine HTML element marker
270 2
		$element = $this->params['widget'] !== '' ? 'li' : 'span';
271
272
		// Add size information
273 2
		foreach ( $tags as $name => $size ) {
274 2
			$htmlTags[] = Html::rawElement(
275 2
				$element,
276
				[
277 2
					'style' => "font-size:$size%" ],
278 2
				$this->tagsHtml[$name]
279
			);
280
		}
281
282
		// Stringify
283 2
		$htmlSTags = implode( ' ', $htmlTags );
284
285
		// Handle sphere/canvas output objects
286 2
		if ( in_array( $this->params['widget'], [ 'sphere', 'wordcloud' ] ) ) {
287
288
			// Wrap LI/UL elements
289
			$htmlCTags = Html::rawElement(
290
				'ul',
291
				[
292
					'style' => 'display:none;'
293
				],
294
				$htmlSTags
295
			);
296
297
			// Wrap tags
298
			$htmlCTags = Html::rawElement(
299
				'div',
300
				[
301
					'id' => $tagId . '-tags',
302
					'class' => 'srf-tags'
303
				],
304
				$htmlCTags
305
			);
306
307
			// Wrap everything in a container object
308
			$htmlSTags = Html::rawElement(
309
				'div',
310
				[
311
					'id' => $tagId . '-container',
312
					'class' => 'srf-container',
313
					'data-width' => $this->params['width'],
314
					'data-height' => $this->params['height'],
315
					'data-font' => $this->params['font']
316
				],
317
				$htmlCTags
318
			);
319
320
			// Processing placeholder
321
			$processing = SRFUtils::htmlProcessingElement();
322
		}
323
324
		// Beautify class selector
325 2
		$class = $this->params['widget'] ? '-' . $this->params['widget'] . ' ' : '';
326 2
		$class = $this->params['class'] ? $class . ' ' . $this->params['class'] : $class;
327
328
		// General placeholder
329
		$attribs = [
330 2
			'class' => 'srf-tagcloud' . $class,
331 2
			'data-version' => '0.4.1'
332
		];
333
334 2
		return Html::rawElement( 'div', $attribs, $processing . $htmlSTags );
335
	}
336
337
	/**
338
	 * @param string $value
339
	 * @param int $rowNumber
340
	 *
341
	 * @return string
342
	 */
343 2
	private function addTemplateOutput( $value, &$rowNumber ) {
344 2
		$rowNumber++;
345 2
		$wikitext = $this->params['userparam'] ? "|userparam=" . $this->params['userparam'] : '';
346 2
		$wikitext .= "|" . $value;
347 2
		$wikitext .= "|#=$rowNumber";
348 2
		return '{{' . trim( $this->params['template'] ) . $wikitext . '}}';
349
	}
350
351
	/**
352
	 * @see ResultPrinter::getParamDefinitions
353
	 *
354
	 * @since 1.8
355
	 *
356
	 * @param $definitions array of IParamDefinition
357
	 *
358
	 * @return array of IParamDefinition|array
359
	 */
360 2
	public function getParamDefinitions( array $definitions ) {
361 2
		$params = parent::getParamDefinitions( $definitions );
362
363 2
		$params['template'] = [
364
			'message' => 'srf-paramdesc-template',
365
			'default' => '',
366
		];
367
368 2
		$params['userparam'] = [
369
			'message' => 'srf-paramdesc-userparam',
370
			'default' => '',
371
		];
372
373 2
		$params['excludetags'] = [
374
			'message' => 'srf-paramdesc-excludetags',
375
			'default' => '',
376
		];
377
378 2
		$params['includesubject'] = [
379
			'type' => 'boolean',
380
			'message' => 'srf-paramdesc-includesubject',
381
			'default' => false,
382
		];
383
384 2
		$params['tagorder'] = [
385
			'message' => 'srf_paramdesc_tagorder',
386
			'default' => 'alphabetical',
387
			'values' => [ 'alphabetical', 'asc', 'desc', 'random', 'unchanged' ],
388
		];
389
390 2
		$params['increase'] = [
391
			'message' => 'srf_paramdesc_increase',
392
			'default' => 'log',
393
			'values' => [ 'linear', 'log' ],
394
		];
395
396 2
		$params['widget'] = [
397
			'message' => 'srf-paramdesc-widget',
398
			'default' => '',
399
			'values' => [ 'sphere', 'wordcloud' ],
400
		];
401
402 2
		$params['class'] = [
403
			'message' => 'srf-paramdesc-class',
404
			'default' => '',
405
		];
406
407 2
		$params['font'] = [
408
			'message' => 'srf-paramdesc-font',
409
			'default' => 'impact',
410
		];
411
412 2
		$params['height'] = [
413
			'type' => 'integer',
414
			'message' => 'srf-paramdesc-height',
415
			'default' => 400,
416
			'lowerbound' => 1,
417
		];
418
419 2
		$params['width'] = [
420
			'type' => 'integer',
421
			'message' => 'srf-paramdesc-width',
422
			'default' => 400,
423
			'lowerbound' => 1,
424
		];
425
426 2
		$params['mincount'] = [
427
			'type' => 'integer',
428
			'message' => 'srf_paramdesc_mincount',
429
			'default' => 1,
430
			'manipulatedefault' => false,
431
		];
432
433 2
		$params['minsize'] = [
434
			'type' => 'integer',
435
			'message' => 'srf_paramdesc_minsize',
436
			'default' => 77,
437
			'manipulatedefault' => false,
438
		];
439
440 2
		$params['maxsize'] = [
441
			'type' => 'integer',
442
			'message' => 'srf_paramdesc_maxsize',
443
			'default' => 242,
444
			'manipulatedefault' => false,
445
		];
446
447 2
		$params['maxtags'] = [
448
			'type' => 'integer',
449
			'message' => 'srf_paramdesc_maxtags',
450
			'default' => 1000,
451
			'lowerbound' => 1,
452
		];
453
454 2
		return $params;
455
	}
456
}
457