Completed
Push — master ( 4bfc28...46a6ac )
by Jeroen De
22s queued 10s
created

QueryHandler::buildLocationsForPage()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 9.7666
c 0
b 0
f 0
cc 4
nc 4
nop 5
crap 4
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 MediaWiki\MediaWikiServices;
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 3
	public function __construct( SMWQueryResult $queryResult, int $outputMode, bool $linkAbsolute = false ) {
102 3
		$this->queryResult = $queryResult;
103 3
		$this->outputMode = $outputMode;
104 3
		$this->linkAbsolute = $linkAbsolute;
105 3
	}
106
107 3
	public function setTemplate( string $template ) {
108 3
		$this->template = $template === '' ? false : $template;
109 3
	}
110
111 3
	public function setUserParam( string $userParam ) {
112 3
		$this->userParam = $userParam;
113 3
	}
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 3
	public function setShowSubject( bool $showSubject ) {
141 3
		$this->showSubject = $showSubject;
142 3
	}
143
144 3
	public function setLinkStyle( string $link ) {
145 3
		$this->linkStyle = $link;
146 3
	}
147
148 3
	public function setHeaderStyle( string $headers ) {
149 3
		$this->headerStyle = $headers;
150 3
	}
151
152
	/**
153
	 * @return Location[]
154
	 */
155 3
	public function getLocations(): iterable {
156 3
		while ( ( $row = $this->queryResult->getNext() ) !== false ) {
157 3
			yield from $this->handlePageResult( $row );
158
		}
159 3
	}
160
161
	/**
162
	 * @param SMWResultArray[] $row
163
	 * @return Location[]
164
	 */
165 3
	private function handlePageResult( array $row ): array {
166 3
		[ $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 3
		[ $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 3
		return $this->buildLocationsForPage(
170 3
			$locations,
171
			$text,
172 3
			$this->getLocationIcon( $row ),
173
			$properties,
174 3
			Title::newFromText( $title )
175
		);
176
	}
177
178
	/**
179
	 * @param SMWResultArray $resultArray
180
	 * @return array|string[] [string $title, string $text]
181
	 */
182 3
	private function getTitleAndText( SMWResultArray $resultArray ): array {
183 3
		while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
184 3
			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...
185
				return [
186 3
					$dataValue->getLongText( $this->outputMode, null ),
187 3
					$this->getResultSubjectText( $dataValue )
188
				];
189
			}
190
191
			if ( $dataValue->getTypeID() == '_str' ) {
192
				return [
193
					$dataValue->getLongText( $this->outputMode, null ),
194
					$dataValue->getLongText( $this->outputMode, smwfGetLinker() )
195
				];
196
			}
197
		}
198
199
		return [ '', '' ];
200
	}
201
202
	/**
203
	 * @param SMWResultArray[] $row
204
	 * @return array
205
	 */
206 3
	private function getLocationsAndProperties( array $row ): array {
207 3
		$locations = [];
208 3
		$properties = [];
209
210
		// Loop through all fields of the record.
211 3
		foreach ( $row as $i => $resultArray ) {
212 3
			if ( $i === 0 ) {
213 3
				continue;
214
			}
215
216
			// Loop through all the parts of the field value.
217 3
			while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
218 3
				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...
219
					foreach ( $dataValue->getDataItems() as $dataItem ) {
220
						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...
221
							$locations[] = $this->locationFromDataItem( $dataItem );
222
						}
223
					}
224 3
				} elseif ( $dataValue instanceof CoordinateValue ) {
225 3
					$locations[] = $this->locationFromDataItem( $dataValue->getDataItem() );
226
				}
227
				else {
228 1
					$properties[$resultArray->getPrintRequest()->getCanonicalLabel()] = $this->handleResultProperty(
229 1
						$dataValue,
230 1
						$resultArray->getPrintRequest()
231
					);
232
				}
233
			}
234
		}
235
236 3
		return [ $locations, $properties ];
237
	}
238
239 3
	private function locationFromDataItem( SMWDIGeoCoord $dataItem ): Location {
240 3
		return Location::newFromLatLon(
241 3
			$dataItem->getLatitude(),
242 3
			$dataItem->getLongitude()
243
		);
244
	}
245
246
	/**
247
	 * Handles a SMWWikiPageValue subject value.
248
	 * Gets the plain text title and creates the HTML text with headers and the like.
249
	 *
250
	 * @param SMWWikiPageValue $object
251
	 *
252
	 * @return string
253
	 */
254 3
	private function getResultSubjectText( SMWWikiPageValue $object ): string {
255 3
		if ( !$this->showSubject ) {
256
			return '';
257
		}
258
259 3
		$dataItem = $object->getDataItem();
260
261 3
		if ( $this->showArticleLink() ) {
262 3
			if ( $this->linkAbsolute ) {
263
				$text = Html::element(
264
					'a',
265
					[ 'href' => $dataItem->getTitle()->getFullUrl() ],
266
					$this->hideNamespace ? $object->getText() : $dataItem->getTitle()->getFullText()
267
				);
268
			} else {
269 3
				if ( $this->hideNamespace ) {
270
					$text = $object->getShortHTMLText( smwfGetLinker() );
271
				} else {
272 3
					$text = $object->getLongHTMLText( smwfGetLinker() );
273
				}
274
			}
275
		} else {
276
			$text = $this->hideNamespace ? $object->getText() : $dataItem->getTitle()->getFullText();
277
		}
278
279 3
		return '<b>' . $text . '</b>';
280
	}
281
282 3
	private function showArticleLink() {
283 3
		return $this->linkStyle !== 'none';
284
	}
285
286
	/**
287
	 * Handles a single property (SMWPrintRequest) to be displayed for a record (SMWDataValue).
288
	 */
289 1
	private function handleResultProperty( SMWDataValue $object, SMWPrintRequest $printRequest ): string {
290 1
		if ( $this->hasTemplate() ) {
291
			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...
292
				return $object->getDataItem()->getTitle()->getPrefixedText();
293
			}
294
295
			return $object->getLongText( SMW_OUTPUT_WIKI, null );
296
		}
297
298 1
		$propertyName = $this->getPropertyName( $printRequest );
299 1
		return $propertyName . ( $propertyName === '' ? '' : ': ' ) . $this->getPropertyValue( $object );
300
	}
301
302 1
	private function getPropertyName( SMWPrintRequest $printRequest ): string {
303 1
		if ( $this->headerStyle === 'hide' ) {
304
			return '';
305
		}
306
307 1
		if ( $this->linkAbsolute ) {
308
			$titleText = $printRequest->getText( null );
309
			$t = Title::newFromText( $titleText, SMW_NS_PROPERTY );
310
311
			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...
312
				return  Html::element(
313
					'a',
314
					[ 'href' => $t->getFullUrl() ],
315
					$printRequest->getHTMLText( null )
316
				);
317
			}
318
319
			return $titleText;
320
		}
321
322 1
		return $printRequest->getHTMLText( $this->getPropertyLinker() );
323
	}
324
325 1
	private function getPropertyLinker(): ?Linker {
326 1
		return $this->headerStyle === 'show' && $this->linkStyle !== 'none' ? smwfGetLinker() : null;
327
	}
328
329 1
	private function getValueLinker(): ?Linker {
330 1
		return $this->linkStyle === 'all' ? smwfGetLinker() : null;
331
	}
332
333 1
	private function getPropertyValue( SMWDataValue $object ): string {
334 1
		if ( !$this->linkAbsolute ) {
335 1
			return $object->getLongHTMLText(
336 1
				$this->getValueLinker()
337
			);
338
		}
339
340
		if ( $this->hasPage( $object ) ) {
341
			return Html::element(
342
				'a',
343
				[
344
					'href' => Title::newFromText(
345
						$object->getLongText( $this->outputMode, null ),
346
						NS_MAIN
347
					)->getFullUrl()
348
				],
349
				$object->getLongText( $this->outputMode, null )
350
			);
351
		}
352
353
		return $object->getLongText( $this->outputMode, null );
354
	}
355
356
	private function hasPage( SMWDataValue $object ): bool {
357
		$hasPage = $object->getTypeID() == '_wpg';
358
359
		if ( $hasPage ) {
360
			$t = Title::newFromText( $object->getLongText( $this->outputMode, null ), NS_MAIN );
361
			$hasPage = $t !== null && $t->exists();
362
		}
363
364
		return $hasPage;
365
	}
366
367 3
	private function hasTemplate() {
368 3
		return is_string( $this->template );
369
	}
370
371
	/**
372
	 * Get the icon for a row.
373
	 *
374
	 * @param array $row
375
	 *
376
	 * @return string
377
	 */
378 3
	private function getLocationIcon( array $row ) {
379 3
		$icon = '';
380 3
		$legendLabels = [];
381
382
		//Check for activeicon parameter
383
384 3
		if ( $this->shouldGetActiveIconUrlFor( $row[0]->getResultSubject()->getTitle() ) ) {
385
			$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...
386
		}
387
388
		// Look for display_options field, which can be set by Semantic Compound Queries
389
		// the location of this field changed in SMW 1.5
390 3
		$display_location = method_exists( $row[0], 'getResultSubject' ) ? $row[0]->getResultSubject() : $row[0];
391
392 3
		if ( property_exists( $display_location, 'display_options' ) && is_array(
393 3
				$display_location->display_options
394
			) ) {
395
			$display_options = $display_location->display_options;
396
			if ( array_key_exists( 'icon', $display_options ) ) {
397
				$icon = $display_options['icon'];
398
399
				// 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
400
				if ( array_key_exists( 'legend label', $display_options ) ) {
401
402
					$legend_label = $display_options['legend label'];
403
404
					if ( !array_key_exists( $icon, $legendLabels ) ) {
405
						$legendLabels[$icon] = $legend_label;
406
					}
407
				}
408
			}
409
		} // Icon can be set even for regular, non-compound queries If it is, though, we have to translate the name into a URL here
410 3
		elseif ( $this->icon !== '' ) {
411
			$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...
412
		}
413
414 3
		return $icon;
415
	}
416
417 3
	private function shouldGetActiveIconUrlFor( Title $title ) {
418 3
		global $wgTitle;
419
420 3
		return isset( $this->activeIcon ) && is_object( $wgTitle )
421 3
			&& $wgTitle->equals( $title );
422
	}
423
424
	/**
425
	 * @param Location[] $locations
426
	 * @param string $text
427
	 * @param string $icon
428
	 * @param array $properties
429
	 * @param Title|null $title
430
	 *
431
	 * @return Location[]
432
	 */
433 3
	private function buildLocationsForPage( array $locations, $text, $icon, array $properties, Title $title = null ): array {
434 3
		$titleOutput = $this->getTitleOutput( $title );
435
436 3
		if ( $properties !== [] && $text !== '' ) {
437 1
			$text .= $this->subjectSeparator;
438
		}
439
440 3
		foreach ( $locations as &$location ) {
441 3
			$location->setTitle( $titleOutput );
442 3
			$location->setText( $text . $this->buildPopupText( $properties, $titleOutput, $location ) );
443 3
			$location->setIcon( trim( $icon ) );
444
		}
445
446 3
		return $locations;
447
	}
448
449 3
	private function buildPopupText( array $properties, string $titleOutput, Location $location ): string {
450 3
		if ( $this->hasTemplate() ) {
451 1
			return $this->getParser()->recursiveTagParseFully(
452 1
				$this->newTemplatedPopup()->getWikiText( $titleOutput, $location->getCoordinates(), $properties )
453
			);
454
		}
455
456 2
		return implode( '<br />', $properties );
457
	}
458
459 1
	private function newTemplatedPopup(): TemplatedPopup {
460 1
		return new TemplatedPopup(
461 1
			$this->template,
0 ignored issues
show
Bug introduced by
It seems like $this->template can also be of type boolean; however, Maps\SemanticMW\TemplatedPopup::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
462 1
			$this->userParam
463
		);
464
	}
465
466 3
	private function getTitleOutput( Title $title = null ) {
467 3
		if ( $title === null ) {
468
			return '';
469
		}
470
471 3
		return $this->hideNamespace ? $title->getText() : $title->getFullText();
472
	}
473
474 1
	private function getParser(): \Parser {
475 1
		return MediaWikiServices::getInstance()->getParser();
476
	}
477
478
	/**
479
	 * @return boolean
480
	 */
481
	public function getHideNamespace() {
482
		return $this->hideNamespace;
483
	}
484
485
	/**
486
	 * @param boolean $hideNamespace
487
	 */
488 3
	public function setHideNamespace( $hideNamespace ) {
489 3
		$this->hideNamespace = $hideNamespace;
490 3
	}
491
492
	/**
493
	 * @return string
494
	 */
495
	public function getActiveIcon() {
496
		return $this->activeIcon;
497
	}
498
499
	/**
500
	 * @param string $activeIcon
501
	 */
502 3
	public function setActiveIcon( $activeIcon ) {
503 3
		$this->activeIcon = $activeIcon;
504 3
	}
505
506
}
507