Completed
Push — move ( 875a6f )
by Jeroen De
04:17
created

QueryHandler::handleResultRow()   D

Complexity

Conditions 19
Paths 22

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 380

Importance

Changes 0
Metric Value
dl 0
loc 74
ccs 0
cts 65
cp 0
rs 4.5166
c 0
b 0
f 0
cc 19
nc 22
nop 1
crap 380

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 Maps\SemanticMW\ResultPrinters;
4
5
use Html;
6
use Maps\Elements\Location;
7
use MapsMapper;
8
use PolygonHandler;
9
use SMWDataValue;
10
use SMWPrintRequest;
11
use SMWQueryResult;
12
use SMWResultArray;
13
use SMWWikiPageValue;
14
use Title;
15
16
/**
17
 * Class for handling geographical SMW queries.
18
 *
19
 * @licence GNU GPL v2+
20
 * @author Jeroen De Dauw < [email protected] >
21
 */
22
class QueryHandler {
23
24
	/**
25
	 * The global icon.
26
	 *
27
	 * @var string
28
	 */
29
	public $icon = '';
30
	/**
31
	 * The global text.
32
	 *
33
	 * @var string
34
	 */
35
	public $text = '';
36
	/**
37
	 * The global title.
38
	 *
39
	 * @var string
40
	 */
41
	public $title = '';
42
	/**
43
	 * Make a separate link to the title or not?
44
	 *
45
	 * @var boolean
46
	 */
47
	public $titleLinkSeparate = false;
48
	private $queryResult;
49
	private $outputMode;
50
	/**
51
	 * @var array
52
	 */
53
	private $geoShapes = [
54
		'lines' => [],
55
		'locations' => [],
56
		'polygons' => []
57
	];
58
	/**
59
	 * The template to use for the text, or false if there is none.
60
	 *
61
	 * @var string|boolean false
62
	 */
63
	private $template = false;
64
	/**
65
	 * Should link targets be made absolute (instead of relative)?
66
	 *
67
	 * @var boolean
68
	 */
69
	private $linkAbsolute;
70
71
	/**
72
	 * The text used for the link to the page (if it's created). $1 will be replaced by the page name.
73
	 *
74
	 * @var string
75
	 */
76
	private $pageLinkText = '$1';
77
78
	/**
79
	 * A separator to use between the subject and properties in the text field.
80
	 *
81
	 * @var string
82
	 */
83
	private $subjectSeparator = '<hr />';
84
85
	/**
86
	 * Make the subject in the text bold or not?
87
	 *
88
	 * @var boolean
89
	 */
90
	private $boldSubject = true;
91
92
	/**
93
	 * Show the subject in the text or not?
94
	 *
95
	 * @var boolean
96
	 */
97
	private $showSubject = true;
98
99
	/**
100
	 * Hide the namespace or not.
101
	 *
102
	 * @var boolean
103
	 */
104
	private $hideNamespace = false;
105
106
	/**
107
	 * Defines which article names in the result are hyperlinked, all normally is the default
108
	 * none, subject, all
109
	 */
110
	private $linkStyle = 'all';
111
112
	/*
113
	 * Show headers (with links), show headers (just text) or hide them. show is default
114
	 * show, plain, hide
115
	 */
116
	private $headerStyle = 'show';
117
118
	/**
119
	 * Marker icon to show when marker equals active page
120
	 *
121
	 * @var string|null
122
	 */
123
	private $activeIcon = null;
124
125
	/**
126
	 * @var string
127
	 */
128
	private $userParam = '';
129
130
	/**
131
	 * @param SMWQueryResult $queryResult
132
	 * @param integer $outputMode
133
	 * @param boolean $linkAbsolute
134
	 */
135
	public function __construct( SMWQueryResult $queryResult, $outputMode, $linkAbsolute = false ) {
136
		$this->queryResult = $queryResult;
137
		$this->outputMode = $outputMode;
138
		$this->linkAbsolute = $linkAbsolute;
139
	}
140
141
	/**
142
	 * Sets the template.
143
	 *
144
	 * @param string $template
145
	 */
146
	public function setTemplate( $template ) {
147
		$this->template = $template === '' ? false : $template;
148
	}
149
150
	/**
151
	 * @param string $userParam
152
	 */
153
	public function setUserParam( $userParam ) {
154
		$this->userParam = $userParam;
155
	}
156
157
	/**
158
	 * Sets the global icon.
159
	 *
160
	 * @param string $icon
161
	 */
162
	public function setIcon( $icon ) {
163
		$this->icon = $icon;
164
	}
165
166
	/**
167
	 * Sets the global title.
168
	 *
169
	 * @param string $title
170
	 */
171
	public function setTitle( $title ) {
172
		$this->title = $title;
173
	}
174
175
	/**
176
	 * Sets the global text.
177
	 *
178
	 * @param string $text
179
	 */
180
	public function setText( $text ) {
181
		$this->text = $text;
182
	}
183
184
	/**
185
	 * Sets the subject separator.
186
	 *
187
	 * @param string $subjectSeparator
188
	 */
189
	public function setSubjectSeparator( $subjectSeparator ) {
190
		$this->subjectSeparator = $subjectSeparator;
191
	}
192
193
	/**
194
	 * Sets if the subject should be made bold in the text.
195
	 *
196
	 * @param string $boldSubject
197
	 */
198
	public function setBoldSubject( $boldSubject ) {
199
		$this->boldSubject = $boldSubject;
0 ignored issues
show
Documentation Bug introduced by
The property $boldSubject was declared of type boolean, but $boldSubject is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
200
	}
201
202
	/**
203
	 * Sets if the subject should shown in the text.
204
	 *
205
	 * @param string $showSubject
206
	 */
207
	public function setShowSubject( $showSubject ) {
208
		$this->showSubject = $showSubject;
0 ignored issues
show
Documentation Bug introduced by
The property $showSubject was declared of type boolean, but $showSubject is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
209
	}
210
211
	/**
212
	 * Sets the text for the link to the page when separate from the title.
213
	 *
214
	 * @param string $text
215
	 */
216
	public function setPageLinkText( $text ) {
217
		$this->pageLinkText = $text;
218
	}
219
220
	/**
221
	 *
222
	 * @param boolean $link
223
	 */
224
	public function setLinkStyle( $link ) {
225
		$this->linkStyle = $link;
0 ignored issues
show
Documentation Bug introduced by
The property $linkStyle was declared of type string, but $link is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
226
	}
227
228
	/**
229
	 *
230
	 * @param boolean $headers
231
	 */
232
	public function setHeaderStyle( $headers ) {
233
		$this->headerStyle = $headers;
0 ignored issues
show
Documentation Bug introduced by
The property $headerStyle was declared of type string, but $headers is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
234
	}
235
236
	/**
237
	 * @return array
238
	 */
239
	public function getShapes() {
240
		$this->findShapes();
241
		return $this->geoShapes;
242
	}
243
244
	/**
245
	 * @since 2.0
246
	 */
247
	private function findShapes() {
248
		while ( ( $row = $this->queryResult->getNext() ) !== false ) {
249
			$this->handleResultRow( $row );
250
		}
251
	}
252
253
	/**
254
	 * Returns the locations found in the provided result row.
255
	 *
256
	 * @param SMWResultArray[] $row
257
	 */
258
	private function handleResultRow( array $row ) {
259
		$locations = [];
260
		$properties = [];
261
262
		$title = '';
263
		$text = '';
264
265
		// Loop through all fields of the record.
266
		foreach ( $row as $i => $resultArray ) {
267
			$printRequest = $resultArray->getPrintRequest();
268
269
			// Loop through all the parts of the field value.
270
			while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
271
				if ( $dataValue->getTypeID() == '_wpg' && $i == 0 ) {
272
					$title = $dataValue->getLongText( $this->outputMode, null );
273
					$text = $this->getResultSubjectText( $dataValue );
274
				} else {
275
					if ( $dataValue->getTypeID() == '_str' && $i == 0 ) {
276
						$title = $dataValue->getLongText( $this->outputMode, null );
277
						$text = $dataValue->getLongText( $this->outputMode, smwfGetLinker() );
278
					} else {
279
						if ( $dataValue->getTypeID() == '_gpo' ) {
280
							$dataItem = $dataValue->getDataItem();
281
							$polyHandler = new PolygonHandler ( $dataItem->getString() );
282
							$this->geoShapes[$polyHandler->getGeoType()][] = $polyHandler->shapeFromText();
283
						} else {
284
							if ( strpos( $dataValue->getTypeID(), '_rec' ) !== false ) {
285
								foreach ( $dataValue->getDataItems() as $dataItem ) {
286
									if ( $dataItem instanceof \SMWDIGeoCoord ) {
0 ignored issues
show
Bug introduced by
The class SMWDIGeoCoord does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
287
										$locations[] = Location::newFromLatLon(
288
											$dataItem->getLatitude(),
289
											$dataItem->getLongitude()
290
										);
291
									}
292
								}
293
							} else {
294
								if ( $dataValue->getTypeID() != '_geo' && $i != 0 && !$this->isHeadersHide() ) {
295
									$properties[] = $this->handleResultProperty( $dataValue, $printRequest );
296
								} else {
297
									if ( $printRequest->getMode(
298
										) == SMWPrintRequest::PRINT_PROP && $printRequest->getTypeID(
299
										) == '_geo' || $dataValue->getTypeID() == '_geo' ) {
300
										$dataItem = $dataValue->getDataItem();
301
302
										$locations[] = Location::newFromLatLon(
303
											$dataItem->getLatitude(),
304
											$dataItem->getLongitude()
305
										);
306
									}
307
								}
308
							}
309
						}
310
					}
311
				}
312
			}
313
		}
314
315
		if ( $properties !== [] && $text !== '' ) {
316
			$text .= $this->subjectSeparator;
317
		}
318
319
		$icon = $this->getLocationIcon( $row );
320
321
		$this->geoShapes['locations'] = array_merge(
322
			$this->geoShapes['locations'],
323
			$this->buildLocationsList(
324
				$locations,
325
				$text,
326
				$icon,
327
				$properties,
328
				Title::newFromText( $title )
329
			)
330
		);
331
	}
332
333
	/**
334
	 * Handles a SMWWikiPageValue subject value.
335
	 * Gets the plain text title and creates the HTML text with headers and the like.
336
	 *
337
	 * @param SMWWikiPageValue $object
338
	 *
339
	 * @return string
340
	 */
341
	private function getResultSubjectText( SMWWikiPageValue $object ): string {
342
		if ( !$this->showSubject ) {
343
			return '';
344
		}
345
346
		if ( $this->showArticleLink() ) {
347
			if ( !$this->titleLinkSeparate && $this->linkAbsolute ) {
348
				$text = Html::element(
349
					'a',
350
					[ 'href' => $object->getTitle()->getFullUrl() ],
351
					$this->hideNamespace ? $object->getText() : $object->getTitle()->getFullText()
352
				);
353
			} else {
354
				if ( $this->hideNamespace ) {
355
					$text = $object->getShortHTMLText( smwfGetLinker() );
356
				} else {
357
					$text = $object->getLongHTMLText( smwfGetLinker() );
358
				}
359
			}
360
		} else {
361
			$text = $this->hideNamespace ? $object->getText() : $object->getTitle()->getFullText();
362
		}
363
364
		if ( $this->boldSubject ) {
365
			$text = '<b>' . $text . '</b>';
366
		}
367
368
		if ( !$this->titleLinkSeparate ) {
369
			return $text;
370
		}
371
372
		$txt = $object->getTitle()->getText();
373
374
		if ( $this->pageLinkText !== '' ) {
375
			$txt = str_replace( '$1', $txt, $this->pageLinkText );
376
		}
377
378
		return $text . Html::element(
379
				'a',
380
				[ 'href' => $object->getTitle()->getFullUrl() ],
381
				$txt
382
			);
383
	}
384
385
	private function showArticleLink() {
386
		return $this->linkStyle !== 'none';
387
	}
388
389
	private function isHeadersHide() {
390
		return $this->headerStyle === 'hide';
391
	}
392
393
	/**
394
	 * Handles a single property (SMWPrintRequest) to be displayed for a record (SMWDataValue).
395
	 *
396
	 * @param SMWDataValue $object
397
	 * @param SMWPrintRequest $printRequest
398
	 *
399
	 * @return string
400
	 */
401
	private function handleResultProperty( SMWDataValue $object, SMWPrintRequest $printRequest ) {
402
		if ( $this->hasTemplate() ) {
403
			if ( $object instanceof SMWWikiPageValue ) {
0 ignored issues
show
Bug introduced by
The class SMWWikiPageValue 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...
404
				return $object->getTitle()->getPrefixedText();
405
			}
406
407
			return $object->getLongText( SMW_OUTPUT_WIKI, null );
408
		}
409
410
		if ( $this->linkAbsolute ) {
411
			$titleText = $printRequest->getText( null );
412
			$t = Title::newFromText( $titleText, SMW_NS_PROPERTY );
413
414
			if ( $this->isHeadersShow() && $t instanceof Title && $t->exists() ) {
0 ignored issues
show
Bug introduced by
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...
415
				$propertyName = $propertyName = Html::element(
416
					'a',
417
					[ 'href' => $t->getFullUrl() ],
418
					$printRequest->getHTMLText( null )
419
				);
420
			} else {
421
				$propertyName = $titleText;
422
			}
423
		} else {
424
			if ( $this->isHeadersShow() ) {
425
				$propertyName = $printRequest->getHTMLText( smwfGetLinker() );
426
			} else {
427
				if ( $this->isHeadersPlain() ) {
428
					$propertyName = $printRequest->getText( null );
429
				}
430
			}
431
		}
432
433
		if ( $this->linkAbsolute ) {
434
			$hasPage = $object->getTypeID() == '_wpg';
435
436
			if ( $hasPage ) {
437
				$t = Title::newFromText( $object->getLongText( $this->outputMode, null ), NS_MAIN );
438
				$hasPage = $t !== null && $t->exists();
439
			}
440
441
			if ( $hasPage ) {
442
				$propertyValue = Html::element(
443
					'a',
444
					[ 'href' => $t->getFullUrl() ],
0 ignored issues
show
Bug introduced by
The variable $t 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...
445
					$object->getLongText( $this->outputMode, null )
446
				);
447
			} else {
448
				$propertyValue = $object->getLongText( $this->outputMode, null );
449
			}
450
		} else {
451
			$propertyValue = $object->getLongText( $this->outputMode, smwfGetLinker() );
452
		}
453
454
		return $propertyName . ( $propertyName === '' ? '' : ': ' ) . $propertyValue;
0 ignored issues
show
Bug introduced by
The variable $propertyName 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...
455
	}
456
457
	private function hasTemplate() {
458
		return is_string( $this->template );
459
	}
460
461
	private function isHeadersShow() {
462
		return $this->headerStyle === 'show';
463
	}
464
465
	private function isHeadersPlain() {
466
		return $this->headerStyle === 'plain';
467
	}
468
469
	/**
470
	 * Get the icon for a row.
471
	 *
472
	 * @param array $row
473
	 *
474
	 * @return string
475
	 */
476
	private function getLocationIcon( array $row ) {
477
		$icon = '';
478
		$legendLabels = [];
479
480
		//Check for activeicon parameter
481
482
		if ( $this->shouldGetActiveIconUrlFor( $row[0]->getResultSubject()->getTitle() ) ) {
483
			$icon = MapsMapper::getFileUrl( $this->activeIcon );
0 ignored issues
show
Deprecated Code introduced by
The method MapsMapper::getFileUrl() has been deprecated.

This method has been deprecated.

Loading history...
484
		}
485
486
		// Look for display_options field, which can be set by Semantic Compound Queries
487
		// the location of this field changed in SMW 1.5
488
		$display_location = method_exists( $row[0], 'getResultSubject' ) ? $row[0]->getResultSubject() : $row[0];
489
490
		if ( property_exists( $display_location, 'display_options' ) && is_array(
491
				$display_location->display_options
492
			) ) {
493
			$display_options = $display_location->display_options;
494
			if ( array_key_exists( 'icon', $display_options ) ) {
495
				$icon = $display_options['icon'];
496
497
				// This is somewhat of a hack - if a legend label has been set, we're getting it for every point, instead of just once per icon
498
				if ( array_key_exists( 'legend label', $display_options ) ) {
499
500
					$legend_label = $display_options['legend label'];
501
502
					if ( !array_key_exists( $icon, $legendLabels ) ) {
503
						$legendLabels[$icon] = $legend_label;
504
					}
505
				}
506
			}
507
		} // Icon can be set even for regular, non-compound queries If it is, though, we have to translate the name into a URL here
508
		elseif ( $this->icon !== '' ) {
509
			$icon = MapsMapper::getFileUrl( $this->icon );
0 ignored issues
show
Deprecated Code introduced by
The method MapsMapper::getFileUrl() has been deprecated.

This method has been deprecated.

Loading history...
510
		}
511
512
		return $icon;
513
	}
514
515
	private function shouldGetActiveIconUrlFor( Title $title ) {
516
		global $wgTitle;
517
518
		return isset( $this->activeIcon ) && is_object( $wgTitle )
519
			&& $wgTitle->equals( $title );
520
	}
521
522
	/**
523
	 * Builds a set of locations with the provided title, text and icon.
524
	 *
525
	 * @param Location[] $locations
526
	 * @param string $text
527
	 * @param string $icon
528
	 * @param array $properties
529
	 * @param Title|null $title
530
	 *
531
	 * @return Location[]
532
	 */
533
	private function buildLocationsList( array $locations, $text, $icon, array $properties, Title $title = null ) {
534
		if ( !$this->hasTemplate() ) {
535
			$text .= implode( '<br />', $properties );
536
		}
537
538
		$titleOutput = $this->getTitleOutput( $title );
539
540
		foreach ( $locations as &$location ) {
541
			if ( $this->hasTemplate() ) {
542
				$segments = array_merge(
543
					[
544
						$this->template,
545
						'title=' . $titleOutput,
546
						'latitude=' . $location->getCoordinates()->getLatitude(),
547
						'longitude=' . $location->getCoordinates()->getLongitude(),
548
						'userparam=' . $this->userParam
549
					],
550
					$properties
551
				);
552
553
				$text .= $this->getParser()->recursiveTagParseFully(
554
					'{{' . implode( '|', $segments ) . '}}'
555
				);
556
			}
557
558
			$location->setTitle( $titleOutput );
559
			$location->setText( $text );
560
			$location->setIcon( trim( $icon ) );
561
		}
562
563
		return $locations;
564
	}
565
566
	private function getTitleOutput( Title $title = null ) {
567
		if ( $title === null ) {
568
			return '';
569
		}
570
571
		return $this->hideNamespace ? $title->getText() : $title->getFullText();
572
	}
573
574
	/**
575
	 * @return \Parser
576
	 */
577
	private function getParser() {
578
		return $GLOBALS['wgParser'];
579
	}
580
581
	/**
582
	 * @return boolean
583
	 */
584
	public function getHideNamespace() {
585
		return $this->hideNamespace;
586
	}
587
588
	/**
589
	 * @param boolean $hideNamespace
590
	 */
591
	public function setHideNamespace( $hideNamespace ) {
592
		$this->hideNamespace = $hideNamespace;
593
	}
594
595
	/**
596
	 * @return string
597
	 */
598
	public function getActiveIcon() {
599
		return $this->activeIcon;
600
	}
601
602
	/**
603
	 * @param string $activeIcon
604
	 */
605
	public function setActiveIcon( $activeIcon ) {
606
		$this->activeIcon = $activeIcon;
607
	}
608
609
}
610