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

formats/tagcloud/TagCloud.php (4 issues)

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() {
0 ignored issues
show
isHTML uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
85 2
		$title = $GLOBALS['wgTitle'];
86
87 2
		if ( $title instanceof Title ) {
0 ignored issues
show
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
					// 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 ) {
0 ignored issues
show
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...
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