Completed
Push — master ( 81538e...c2bf57 )
by Jeroen De
10:06
created

QueryHandler   F

Complexity

Total Complexity 82

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 82
lcom 1
cbo 4
dl 0
loc 490
rs 2
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setTemplate() 0 3 2
A setUserParam() 0 3 1
A setIcon() 0 3 1
A setTitle() 0 3 1
A setText() 0 3 1
A setSubjectSeparator() 0 3 1
A setShowSubject() 0 3 1
A setLinkStyle() 0 3 1
A setHeaderStyle() 0 3 1
A getLocations() 0 5 2
A handlePageResult() 0 18 3
A getTitleAndText() 0 19 4
B getLocationsAndProperties() 0 32 8
A locationFromDataItem() 0 6 1
B getResultSubjectText() 0 27 7
A showArticleLink() 0 3 1
A handleResultProperty() 0 12 4
A getPropertyName() 0 22 5
A getPropertyLinker() 0 3 3
A getValueLinker() 0 3 2
A getPropertyValue() 0 22 3
A hasPage() 0 10 3
A hasTemplate() 0 3 1
B getLocationIcon() 0 38 9
A shouldGetActiveIconUrlFor() 0 6 3
A buildLocationsList() 0 32 4
A getTitleOutput() 0 7 3
A getParser() 0 3 1
A getHideNamespace() 0 3 1
A setHideNamespace() 0 3 1
A getActiveIcon() 0 3 1
A setActiveIcon() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like QueryHandler 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 QueryHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace Maps\SemanticMW;
6
7
use Html;
8
use Linker;
9
use Maps\LegacyModel\Location;
10
use Maps\MapsFunctions;
11
use Maps\SemanticMW\CoordinateValue;
12
use SMWDataValue;
13
use SMWDIGeoCoord;
14
use SMWPrintRequest;
15
use SMWQueryResult;
16
use SMWResultArray;
17
use SMWWikiPageValue;
18
use Title;
19
20
/**
21
 * @licence GNU GPL v2+
22
 * @author Jeroen De Dauw < [email protected] >
23
 */
24
class QueryHandler {
25
26
	/**
27
	 * The global icon.
28
	 * @var string
29
	 */
30
	public $icon = '';
31
32
	/**
33
	 * The global text.
34
	 * @var string
35
	 */
36
	public $text = '';
37
38
	/**
39
	 * The global title.
40
	 * @var string
41
	 */
42
	public $title = '';
43
44
	private $queryResult;
45
46
	private $outputMode;
47
48
	/**
49
	 * The template to use for the text, or false if there is none.
50
	 * @var string|boolean false
51
	 */
52
	private $template = false;
53
54
	/**
55
	 * Should link targets be made absolute (instead of relative)?
56
	 * @var boolean
57
	 */
58
	private $linkAbsolute;
59
60
	/**
61
	 * A separator to use between the subject and properties in the text field.
62
	 * @var string
63
	 */
64
	private $subjectSeparator = '<hr />';
65
66
	/**
67
	 * Show the subject in the text or not?
68
	 * @var boolean
69
	 */
70
	private $showSubject = true;
71
72
	/**
73
	 * Hide the namespace or not.
74
	 * @var boolean
75
	 */
76
	private $hideNamespace = false;
77
78
	/**
79
	 * Defines which article names in the result are hyperlinked, all normally is the default
80
	 * none, subject, all
81
	 */
82
	private $linkStyle = 'all';
83
84
	/*
85
	 * Show headers (with links), show headers (just text) or hide them. show is default
86
	 * show, plain, hide
87
	 */
88
	private $headerStyle = 'show';
89
90
	/**
91
	 * Marker icon to show when marker equals active page
92
	 * @var string|null
93
	 */
94
	private $activeIcon = null;
95
96
	/**
97
	 * @var string
98
	 */
99
	private $userParam = '';
100
101
	public function __construct( SMWQueryResult $queryResult, int $outputMode, bool $linkAbsolute = false ) {
102
		$this->queryResult = $queryResult;
103
		$this->outputMode = $outputMode;
104
		$this->linkAbsolute = $linkAbsolute;
105
	}
106
107
	public function setTemplate( string $template ) {
108
		$this->template = $template === '' ? false : $template;
109
	}
110
111
	public function setUserParam( string $userParam ) {
112
		$this->userParam = $userParam;
113
	}
114
115
	/**
116
	 * Sets the global icon.
117
	 */
118
	public function setIcon( string $icon ) {
119
		$this->icon = $icon;
120
	}
121
122
	/**
123
	 * Sets the global title.
124
	 */
125
	public function setTitle( string $title ) {
126
		$this->title = $title;
127
	}
128
129
	/**
130
	 * Sets the global text.
131
	 */
132
	public function setText( string $text ) {
133
		$this->text = $text;
134
	}
135
136
	public function setSubjectSeparator( string $subjectSeparator ) {
137
		$this->subjectSeparator = $subjectSeparator;
138
	}
139
140
	public function setShowSubject( bool $showSubject ) {
141
		$this->showSubject = $showSubject;
142
	}
143
144
	public function setLinkStyle( string $link ) {
145
		$this->linkStyle = $link;
146
	}
147
148
	public function setHeaderStyle( string $headers ) {
149
		$this->headerStyle = $headers;
150
	}
151
152
	/**
153
	 * @return Location[]
154
	 */
155
	public function getLocations(): iterable {
156
		while ( ( $row = $this->queryResult->getNext() ) !== false ) {
157
			yield from $this->handlePageResult( $row );
158
		}
159
	}
160
161
	/**
162
	 * @param SMWResultArray[] $row
163
	 * @return Location[]
164
	 */
165
	private function handlePageResult( array $row ): array {
166
		[ $title, $text ] = $this->getTitleAndText( $row[0] );
0 ignored issues
show
Bug introduced by
The variable $title does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $text does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
167
		[ $locations, $properties ] = $this->getLocationsAndProperties( $row );
0 ignored issues
show
Bug introduced by
The variable $locations does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $properties does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
168
169
		if ( $properties !== [] && $text !== '' ) {
170
			$text .= $this->subjectSeparator;
171
		}
172
173
		$icon = $this->getLocationIcon( $row );
174
175
		return $this->buildLocationsList(
176
			$locations,
177
			$text,
178
			$icon,
179
			$properties,
180
			Title::newFromText( $title )
181
		);
182
	}
183
184
	private function getTitleAndText( SMWResultArray $resultArray ): array {
185
		while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
186
			if ( $dataValue 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...
187
				return [
188
					$dataValue->getLongText( $this->outputMode, null ),
189
					$this->getResultSubjectText( $dataValue )
190
				];
191
			}
192
193
			if ( $dataValue->getTypeID() == '_str' ) {
194
				return [
195
					$dataValue->getLongText( $this->outputMode, null ),
196
					$dataValue->getLongText( $this->outputMode, smwfGetLinker() )
197
				];
198
			}
199
		}
200
201
		return [ '', '' ];
202
	}
203
204
	/**
205
	 * @param SMWResultArray[] $row
206
	 * @return array
207
	 */
208
	private function getLocationsAndProperties( array $row ): array {
209
		$locations = [];
210
		$properties = [];
211
212
		// Loop through all fields of the record.
213
		foreach ( $row as $i => $resultArray ) {
214
			if ( $i === 0 ) {
215
				continue;
216
			}
217
218
			// Loop through all the parts of the field value.
219
			while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
220
				if ( $dataValue instanceof \SMWRecordValue ) {
0 ignored issues
show
Bug introduced by
The class SMWRecordValue 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...
221
					foreach ( $dataValue->getDataItems() as $dataItem ) {
222
						if ( $dataItem instanceof \SMWDIGeoCoord ) {
0 ignored issues
show
Bug introduced by
The class SMWDIGeoCoord 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...
223
							$locations[] = $this->locationFromDataItem( $dataItem );
224
						}
225
					}
226
				} elseif ( $dataValue instanceof CoordinateValue ) {
227
					$locations[] = $this->locationFromDataItem( $dataValue->getDataItem() );
228
				}
229
				else {
230
					$properties[] = $this->handleResultProperty(
231
						$dataValue,
232
						$resultArray->getPrintRequest()
233
					);
234
				}
235
			}
236
		}
237
238
		return [ $locations, $properties ];
239
	}
240
241
	private function locationFromDataItem( SMWDIGeoCoord $dataItem ): Location {
242
		return Location::newFromLatLon(
243
			$dataItem->getLatitude(),
244
			$dataItem->getLongitude()
245
		);
246
	}
247
248
	/**
249
	 * Handles a SMWWikiPageValue subject value.
250
	 * Gets the plain text title and creates the HTML text with headers and the like.
251
	 *
252
	 * @param SMWWikiPageValue $object
253
	 *
254
	 * @return string
255
	 */
256
	private function getResultSubjectText( SMWWikiPageValue $object ): string {
257
		if ( !$this->showSubject ) {
258
			return '';
259
		}
260
261
		$dataItem = $object->getDataItem();
262
263
		if ( $this->showArticleLink() ) {
264
			if ( $this->linkAbsolute ) {
265
				$text = Html::element(
266
					'a',
267
					[ 'href' => $dataItem->getTitle()->getFullUrl() ],
268
					$this->hideNamespace ? $object->getText() : $dataItem->getTitle()->getFullText()
269
				);
270
			} else {
271
				if ( $this->hideNamespace ) {
272
					$text = $object->getShortHTMLText( smwfGetLinker() );
273
				} else {
274
					$text = $object->getLongHTMLText( smwfGetLinker() );
275
				}
276
			}
277
		} else {
278
			$text = $this->hideNamespace ? $object->getText() : $dataItem->getTitle()->getFullText();
279
		}
280
281
		return '<b>' . $text . '</b>';
282
	}
283
284
	private function showArticleLink() {
285
		return $this->linkStyle !== 'none';
286
	}
287
288
	/**
289
	 * Handles a single property (SMWPrintRequest) to be displayed for a record (SMWDataValue).
290
	 */
291
	private function handleResultProperty( SMWDataValue $object, SMWPrintRequest $printRequest ): string {
292
		if ( $this->hasTemplate() ) {
293
			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...
294
				return $object->getDataItem()->getTitle()->getPrefixedText();
295
			}
296
297
			return $object->getLongText( SMW_OUTPUT_WIKI, null );
298
		}
299
300
		$propertyName = $this->getPropertyName( $printRequest );
301
		return $propertyName . ( $propertyName === '' ? '' : ': ' ) . $this->getPropertyValue( $object );
302
	}
303
304
	private function getPropertyName( SMWPrintRequest $printRequest ): string {
305
		if ( $this->headerStyle === 'hide' ) {
306
			return '';
307
		}
308
309
		if ( $this->linkAbsolute ) {
310
			$titleText = $printRequest->getText( null );
311
			$t = Title::newFromText( $titleText, SMW_NS_PROPERTY );
312
313
			if ( $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...
314
				return  Html::element(
315
					'a',
316
					[ 'href' => $t->getFullUrl() ],
317
					$printRequest->getHTMLText( null )
318
				);
319
			}
320
321
			return $titleText;
322
		}
323
324
		return $printRequest->getHTMLText( $this->getPropertyLinker() );
325
	}
326
327
	private function getPropertyLinker(): ?Linker {
328
		return $this->headerStyle === 'show' && $this->linkStyle !== 'none' ? smwfGetLinker() : null;
329
	}
330
331
	private function getValueLinker(): ?Linker {
332
		return $this->linkStyle === 'all' ? smwfGetLinker() : null;
333
	}
334
335
	private function getPropertyValue( SMWDataValue $object ): string {
336
		if ( !$this->linkAbsolute ) {
337
			return $object->getLongHTMLText(
338
				$this->getValueLinker()
339
			);
340
		}
341
342
		if ( $this->hasPage( $object ) ) {
343
			return Html::element(
344
				'a',
345
				[
346
					'href' => Title::newFromText(
347
						$object->getLongText( $this->outputMode, null ),
348
						NS_MAIN
349
					)->getFullUrl()
350
				],
351
				$object->getLongText( $this->outputMode, null )
352
			);
353
		}
354
355
		return $object->getLongText( $this->outputMode, null );
356
	}
357
358
	private function hasPage( SMWDataValue $object ): bool {
359
		$hasPage = $object->getTypeID() == '_wpg';
360
361
		if ( $hasPage ) {
362
			$t = Title::newFromText( $object->getLongText( $this->outputMode, null ), NS_MAIN );
363
			$hasPage = $t !== null && $t->exists();
364
		}
365
366
		return $hasPage;
367
	}
368
369
	private function hasTemplate() {
370
		return is_string( $this->template );
371
	}
372
373
	/**
374
	 * Get the icon for a row.
375
	 *
376
	 * @param array $row
377
	 *
378
	 * @return string
379
	 */
380
	private function getLocationIcon( array $row ) {
381
		$icon = '';
382
		$legendLabels = [];
383
384
		//Check for activeicon parameter
385
386
		if ( $this->shouldGetActiveIconUrlFor( $row[0]->getResultSubject()->getTitle() ) ) {
387
			$icon = MapsFunctions::getFileUrl( $this->activeIcon );
0 ignored issues
show
Deprecated Code introduced by
The method Maps\MapsFunctions::getFileUrl() has been deprecated.

This method has been deprecated.

Loading history...
388
		}
389
390
		// Look for display_options field, which can be set by Semantic Compound Queries
391
		// the location of this field changed in SMW 1.5
392
		$display_location = method_exists( $row[0], 'getResultSubject' ) ? $row[0]->getResultSubject() : $row[0];
393
394
		if ( property_exists( $display_location, 'display_options' ) && is_array(
395
				$display_location->display_options
396
			) ) {
397
			$display_options = $display_location->display_options;
398
			if ( array_key_exists( 'icon', $display_options ) ) {
399
				$icon = $display_options['icon'];
400
401
				// 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
402
				if ( array_key_exists( 'legend label', $display_options ) ) {
403
404
					$legend_label = $display_options['legend label'];
405
406
					if ( !array_key_exists( $icon, $legendLabels ) ) {
407
						$legendLabels[$icon] = $legend_label;
408
					}
409
				}
410
			}
411
		} // Icon can be set even for regular, non-compound queries If it is, though, we have to translate the name into a URL here
412
		elseif ( $this->icon !== '' ) {
413
			$icon = MapsFunctions::getFileUrl( $this->icon );
0 ignored issues
show
Deprecated Code introduced by
The method Maps\MapsFunctions::getFileUrl() has been deprecated.

This method has been deprecated.

Loading history...
414
		}
415
416
		return $icon;
417
	}
418
419
	private function shouldGetActiveIconUrlFor( Title $title ) {
420
		global $wgTitle;
421
422
		return isset( $this->activeIcon ) && is_object( $wgTitle )
423
			&& $wgTitle->equals( $title );
424
	}
425
426
	/**
427
	 * Builds a set of locations with the provided title, text and icon.
428
	 *
429
	 * @param Location[] $locations
430
	 * @param string $text
431
	 * @param string $icon
432
	 * @param array $properties
433
	 * @param Title|null $title
434
	 *
435
	 * @return Location[]
436
	 */
437
	private function buildLocationsList( array $locations, $text, $icon, array $properties, Title $title = null ): array {
438
		if ( !$this->hasTemplate() ) {
439
			$text .= implode( '<br />', $properties );
440
		}
441
442
		$titleOutput = $this->getTitleOutput( $title );
443
444
		foreach ( $locations as &$location ) {
445
			if ( $this->hasTemplate() ) {
446
				$segments = array_merge(
447
					[
448
						$this->template,
449
						'title=' . $titleOutput,
450
						'latitude=' . $location->getCoordinates()->getLatitude(),
451
						'longitude=' . $location->getCoordinates()->getLongitude(),
452
						'userparam=' . $this->userParam
453
					],
454
					$properties
455
				);
456
457
				$text .= $this->getParser()->recursiveTagParseFully(
458
					'{{' . implode( '|', $segments ) . '}}'
459
				);
460
			}
461
462
			$location->setTitle( $titleOutput );
463
			$location->setText( $text );
464
			$location->setIcon( trim( $icon ) );
465
		}
466
467
		return $locations;
468
	}
469
470
	private function getTitleOutput( Title $title = null ) {
471
		if ( $title === null ) {
472
			return '';
473
		}
474
475
		return $this->hideNamespace ? $title->getText() : $title->getFullText();
476
	}
477
478
	/**
479
	 * @return \Parser
480
	 */
481
	private function getParser() {
482
		return $GLOBALS['wgParser'];
483
	}
484
485
	/**
486
	 * @return boolean
487
	 */
488
	public function getHideNamespace() {
489
		return $this->hideNamespace;
490
	}
491
492
	/**
493
	 * @param boolean $hideNamespace
494
	 */
495
	public function setHideNamespace( $hideNamespace ) {
496
		$this->hideNamespace = $hideNamespace;
497
	}
498
499
	/**
500
	 * @return string
501
	 */
502
	public function getActiveIcon() {
503
		return $this->activeIcon;
504
	}
505
506
	/**
507
	 * @param string $activeIcon
508
	 */
509
	public function setActiveIcon( $activeIcon ) {
510
		$this->activeIcon = $activeIcon;
511
	}
512
513
}
514