Completed
Push — master ( af1f28...b949e2 )
by Jeroen De
10:06
created

QueryHandler::locationFromDataItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Maps\SemanticMW\ResultPrinters;
4
5
use Html;
6
use Linker;
7
use Maps\Elements\Location;
8
use Maps\MapsFunctions;
9
use Maps\SemanticMW\DataValues\CoordinateValue;
10
use SMWDataValue;
11
use SMWDIGeoCoord;
12
use SMWPrintRequest;
13
use SMWQueryResult;
14
use SMWResultArray;
15
use SMWWikiPageValue;
16
use Title;
17
18
/**
19
 * Class for handling geographical SMW queries.
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
	/**
45
	 * Make a separate link to the title or not?
46
	 * @var boolean
47
	 */
48
	public $titleLinkSeparate = false;
49
50
	private $queryResult;
51
52
	private $outputMode;
53
54
	/**
55
	 * @var array
56
	 */
57
	private $geoShapes = [
58
		'lines' => [],
59
		'locations' => [],
60
		'polygons' => []
61
	];
62
63
	/**
64
	 * The template to use for the text, or false if there is none.
65
	 * @var string|boolean false
66
	 */
67
	private $template = false;
68
69
	/**
70
	 * Should link targets be made absolute (instead of relative)?
71
	 * @var boolean
72
	 */
73
	private $linkAbsolute;
74
75
	/**
76
	 * The text used for the link to the page (if it's created). $1 will be replaced by the page name.
77
	 * @var string
78
	 */
79
	private $pageLinkText = '$1';
80
81
	/**
82
	 * A separator to use between the subject and properties in the text field.
83
	 * @var string
84
	 */
85
	private $subjectSeparator = '<hr />';
86
87
	/**
88
	 * Show the subject in the text or not?
89
	 * @var boolean
90
	 */
91
	private $showSubject = true;
92
93
	/**
94
	 * Hide the namespace or not.
95
	 * @var boolean
96
	 */
97
	private $hideNamespace = false;
98
99
	/**
100
	 * Defines which article names in the result are hyperlinked, all normally is the default
101
	 * none, subject, all
102
	 */
103
	private $linkStyle = 'all';
104
105
	/*
106
	 * Show headers (with links), show headers (just text) or hide them. show is default
107
	 * show, plain, hide
108
	 */
109
	private $headerStyle = 'show';
110
111
	/**
112
	 * Marker icon to show when marker equals active page
113
	 * @var string|null
114
	 */
115
	private $activeIcon = null;
116
117
	/**
118
	 * @var string
119
	 */
120
	private $userParam = '';
121
122
	public function __construct( SMWQueryResult $queryResult, int $outputMode, bool $linkAbsolute = false ) {
123
		$this->queryResult = $queryResult;
124
		$this->outputMode = $outputMode;
125
		$this->linkAbsolute = $linkAbsolute;
126
	}
127
128
	public function setTemplate( string $template ) {
129
		$this->template = $template === '' ? false : $template;
130
	}
131
132
	public function setUserParam( string $userParam ) {
133
		$this->userParam = $userParam;
134
	}
135
136
	/**
137
	 * Sets the global icon.
138
	 */
139
	public function setIcon( string $icon ) {
140
		$this->icon = $icon;
141
	}
142
143
	/**
144
	 * Sets the global title.
145
	 */
146
	public function setTitle( string $title ) {
147
		$this->title = $title;
148
	}
149
150
	/**
151
	 * Sets the global text.
152
	 */
153
	public function setText( string $text ) {
154
		$this->text = $text;
155
	}
156
157
	public function setSubjectSeparator( string $subjectSeparator ) {
158
		$this->subjectSeparator = $subjectSeparator;
159
	}
160
161
	public function setShowSubject( bool $showSubject ) {
162
		$this->showSubject = $showSubject;
163
	}
164
165
	/**
166
	 * Sets the text for the link to the page when separate from the title.
167
	 */
168
	public function setPageLinkText( string $text ) {
169
		$this->pageLinkText = $text;
170
	}
171
172
	public function setLinkStyle( string $link ) {
173
		$this->linkStyle = $link;
174
	}
175
176
	public function setHeaderStyle( string $headers ) {
177
		$this->headerStyle = $headers;
178
	}
179
180
	/**
181
	 * @return array
182
	 */
183
	public function getShapes() {
184
		$this->findShapes();
185
		return $this->geoShapes;
186
	}
187
188
	/**
189
	 * @since 2.0
190
	 */
191
	private function findShapes() {
192
		while ( ( $row = $this->queryResult->getNext() ) !== false ) {
193
			$this->handleResultRow( $row );
194
		}
195
	}
196
197
	private function getTitleAndText( SMWResultArray $resultArray ): array {
198
		while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
199
			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...
200
				return [
201
					$dataValue->getLongText( $this->outputMode, null ),
202
					$this->getResultSubjectText( $dataValue )
203
				];
204
			}
205
206
			if ( $dataValue->getTypeID() == '_str' ) {
207
				return [
208
					$dataValue->getLongText( $this->outputMode, null ),
209
					$dataValue->getLongText( $this->outputMode, smwfGetLinker() )
210
				];
211
			}
212
		}
213
214
		return [ '', '' ];
215
	}
216
217
	/**
218
	 * Returns the locations found in the provided result row.
219
	 *
220
	 * @param SMWResultArray[] $row
221
	 */
222
	private function handleResultRow( array $row ) {
223
		$locations = [];
224
		$properties = [];
225
226
		[ $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...
227
228
		// Loop through all fields of the record.
229
		foreach ( $row as $i => $resultArray ) {
230
			if ( $i === 0 ) {
231
				continue;
232
			}
233
234
			// Loop through all the parts of the field value.
235
			while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
236
				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...
237
					foreach ( $dataValue->getDataItems() as $dataItem ) {
238
						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...
239
							$locations[] = $this->locationFromDataItem( $dataItem );
240
						}
241
					}
242
				} elseif ( $dataValue instanceof CoordinateValue ) {
243
					$locations[] = $this->locationFromDataItem( $dataValue->getDataItem() );
244
				}
245
				else {
246
					$properties[] = $this->handleResultProperty(
247
						$dataValue,
248
						$resultArray->getPrintRequest()
249
					);
250
				}
251
			}
252
		}
253
254
		if ( $properties !== [] && $text !== '' ) {
255
			$text .= $this->subjectSeparator;
256
		}
257
258
		$icon = $this->getLocationIcon( $row );
259
260
		$this->geoShapes['locations'] = array_merge(
261
			$this->geoShapes['locations'],
262
			$this->buildLocationsList(
263
				$locations,
264
				$text,
265
				$icon,
266
				$properties,
267
				Title::newFromText( $title )
268
			)
269
		);
270
	}
271
272
	private function locationFromDataItem( SMWDIGeoCoord $dataItem ): Location {
273
		return Location::newFromLatLon(
274
			$dataItem->getLatitude(),
275
			$dataItem->getLongitude()
276
		);
277
	}
278
279
	/**
280
	 * Handles a SMWWikiPageValue subject value.
281
	 * Gets the plain text title and creates the HTML text with headers and the like.
282
	 *
283
	 * @param SMWWikiPageValue $object
284
	 *
285
	 * @return string
286
	 */
287
	private function getResultSubjectText( SMWWikiPageValue $object ): string {
288
		if ( !$this->showSubject ) {
289
			return '';
290
		}
291
292
		if ( $this->showArticleLink() ) {
293
			if ( !$this->titleLinkSeparate && $this->linkAbsolute ) {
294
				$text = Html::element(
295
					'a',
296
					[ 'href' => $object->getTitle()->getFullUrl() ],
297
					$this->hideNamespace ? $object->getText() : $object->getTitle()->getFullText()
298
				);
299
			} else {
300
				if ( $this->hideNamespace ) {
301
					$text = $object->getShortHTMLText( smwfGetLinker() );
302
				} else {
303
					$text = $object->getLongHTMLText( smwfGetLinker() );
304
				}
305
			}
306
		} else {
307
			$text = $this->hideNamespace ? $object->getText() : $object->getTitle()->getFullText();
308
		}
309
310
		$text = '<b>' . $text . '</b>';
311
312
		if ( !$this->titleLinkSeparate ) {
313
			return $text;
314
		}
315
316
		$txt = $object->getTitle()->getText();
317
318
		if ( $this->pageLinkText !== '' ) {
319
			$txt = str_replace( '$1', $txt, $this->pageLinkText );
320
		}
321
322
		return $text . Html::element(
323
			'a',
324
			[ 'href' => $object->getTitle()->getFullUrl() ],
325
			$txt
326
		);
327
	}
328
329
	private function showArticleLink() {
330
		return $this->linkStyle !== 'none';
331
	}
332
333
	private function isHeadersHide() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
334
		return $this->headerStyle === 'hide';
335
	}
336
337
	/**
338
	 * Handles a single property (SMWPrintRequest) to be displayed for a record (SMWDataValue).
339
	 */
340
	private function handleResultProperty( SMWDataValue $object, SMWPrintRequest $printRequest ): string {
341
		if ( $this->hasTemplate() ) {
342
			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...
343
				return $object->getTitle()->getPrefixedText();
344
			}
345
346
			return $object->getLongText( SMW_OUTPUT_WIKI, null );
347
		}
348
349
		$propertyName = $this->getPropertyName( $printRequest );
350
		return $propertyName . ( $propertyName === '' ? '' : ': ' ) . $this->getPropertyValue( $object );
351
	}
352
353
	private function getPropertyName( SMWPrintRequest $printRequest ): string {
354
		if ( $this->headerStyle === 'hide' ) {
355
			return '';
356
		}
357
358
		if ( $this->linkAbsolute ) {
359
			$titleText = $printRequest->getText( null );
360
			$t = Title::newFromText( $titleText, SMW_NS_PROPERTY );
361
362
			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...
363
				return  Html::element(
364
					'a',
365
					[ 'href' => $t->getFullUrl() ],
366
					$printRequest->getHTMLText( null )
367
				);
368
			}
369
370
			return $titleText;
371
		}
372
373
		return $printRequest->getHTMLText( $this->getPropertyLinker() );
374
	}
375
376
	private function getPropertyLinker(): ?Linker {
377
		return $this->headerStyle === 'show' && $this->linkStyle !== 'none' ? smwfGetLinker() : null;
378
	}
379
380
	private function getValueLinker(): ?Linker {
381
		return $this->linkStyle === 'all' ? smwfGetLinker() : null;
382
	}
383
384
	private function getPropertyValue( SMWDataValue $object ): string {
385
		if ( !$this->linkAbsolute ) {
386
			return $object->getLongHTMLText(
387
				$this->getValueLinker()
388
			);
389
		}
390
391
		if ( $this->hasPage( $object ) ) {
392
			return Html::element(
393
				'a',
394
				[
395
					'href' => Title::newFromText(
396
						$object->getLongText( $this->outputMode, null ),
397
						NS_MAIN
398
					)->getFullUrl()
399
				],
400
				$object->getLongText( $this->outputMode, null )
401
			);
402
		}
403
404
		return $object->getLongText( $this->outputMode, null );
405
	}
406
407
	private function hasPage( SMWDataValue $object ): bool {
408
		$hasPage = $object->getTypeID() == '_wpg';
409
410
		if ( $hasPage ) {
411
			$t = Title::newFromText( $object->getLongText( $this->outputMode, null ), NS_MAIN );
412
			$hasPage = $t !== null && $t->exists();
413
		}
414
415
		return $hasPage;
416
	}
417
418
	private function hasTemplate() {
419
		return is_string( $this->template );
420
	}
421
422
	/**
423
	 * Get the icon for a row.
424
	 *
425
	 * @param array $row
426
	 *
427
	 * @return string
428
	 */
429
	private function getLocationIcon( array $row ) {
430
		$icon = '';
431
		$legendLabels = [];
432
433
		//Check for activeicon parameter
434
435
		if ( $this->shouldGetActiveIconUrlFor( $row[0]->getResultSubject()->getTitle() ) ) {
436
			$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...
437
		}
438
439
		// Look for display_options field, which can be set by Semantic Compound Queries
440
		// the location of this field changed in SMW 1.5
441
		$display_location = method_exists( $row[0], 'getResultSubject' ) ? $row[0]->getResultSubject() : $row[0];
442
443
		if ( property_exists( $display_location, 'display_options' ) && is_array(
444
				$display_location->display_options
445
			) ) {
446
			$display_options = $display_location->display_options;
447
			if ( array_key_exists( 'icon', $display_options ) ) {
448
				$icon = $display_options['icon'];
449
450
				// 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
451
				if ( array_key_exists( 'legend label', $display_options ) ) {
452
453
					$legend_label = $display_options['legend label'];
454
455
					if ( !array_key_exists( $icon, $legendLabels ) ) {
456
						$legendLabels[$icon] = $legend_label;
457
					}
458
				}
459
			}
460
		} // Icon can be set even for regular, non-compound queries If it is, though, we have to translate the name into a URL here
461
		elseif ( $this->icon !== '' ) {
462
			$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...
463
		}
464
465
		return $icon;
466
	}
467
468
	private function shouldGetActiveIconUrlFor( Title $title ) {
469
		global $wgTitle;
470
471
		return isset( $this->activeIcon ) && is_object( $wgTitle )
472
			&& $wgTitle->equals( $title );
473
	}
474
475
	/**
476
	 * Builds a set of locations with the provided title, text and icon.
477
	 *
478
	 * @param Location[] $locations
479
	 * @param string $text
480
	 * @param string $icon
481
	 * @param array $properties
482
	 * @param Title|null $title
483
	 *
484
	 * @return Location[]
485
	 */
486
	private function buildLocationsList( array $locations, $text, $icon, array $properties, Title $title = null ) {
487
		if ( !$this->hasTemplate() ) {
488
			$text .= implode( '<br />', $properties );
489
		}
490
491
		$titleOutput = $this->getTitleOutput( $title );
492
493
		foreach ( $locations as &$location ) {
494
			if ( $this->hasTemplate() ) {
495
				$segments = array_merge(
496
					[
497
						$this->template,
498
						'title=' . $titleOutput,
499
						'latitude=' . $location->getCoordinates()->getLatitude(),
500
						'longitude=' . $location->getCoordinates()->getLongitude(),
501
						'userparam=' . $this->userParam
502
					],
503
					$properties
504
				);
505
506
				$text .= $this->getParser()->recursiveTagParseFully(
507
					'{{' . implode( '|', $segments ) . '}}'
508
				);
509
			}
510
511
			$location->setTitle( $titleOutput );
512
			$location->setText( $text );
513
			$location->setIcon( trim( $icon ) );
514
		}
515
516
		return $locations;
517
	}
518
519
	private function getTitleOutput( Title $title = null ) {
520
		if ( $title === null ) {
521
			return '';
522
		}
523
524
		return $this->hideNamespace ? $title->getText() : $title->getFullText();
525
	}
526
527
	/**
528
	 * @return \Parser
529
	 */
530
	private function getParser() {
531
		return $GLOBALS['wgParser'];
532
	}
533
534
	/**
535
	 * @return boolean
536
	 */
537
	public function getHideNamespace() {
538
		return $this->hideNamespace;
539
	}
540
541
	/**
542
	 * @param boolean $hideNamespace
543
	 */
544
	public function setHideNamespace( $hideNamespace ) {
545
		$this->hideNamespace = $hideNamespace;
546
	}
547
548
	/**
549
	 * @return string
550
	 */
551
	public function getActiveIcon() {
552
		return $this->activeIcon;
553
	}
554
555
	/**
556
	 * @param string $activeIcon
557
	 */
558
	public function setActiveIcon( $activeIcon ) {
559
		$this->activeIcon = $activeIcon;
560
	}
561
562
}
563