MapPrinter::handleMarkerData()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 12
cts 12
cp 1
rs 9.52
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace Maps\Map\SemanticFormat;
6
7
use Linker;
8
use Maps\FileUrlFinder;
9
use Maps\LegacyModel\BaseElement;
10
use Maps\LegacyModel\Location;
11
use Maps\Map\MapOutput;
12
use Maps\Map\MapOutputBuilder;
13
use Maps\MappingService;
14
use Maps\Presentation\ElementJsonSerializer;
15
use Maps\Presentation\WikitextParser;
16
use Maps\SemanticMW\QueryHandler;
17
use Maps\WikitextParsers\LocationParser;
18
use MediaWiki\MediaWikiServices;
19
use Parser;
20
use SMW\Query\ResultPrinters\ResultPrinter;
21
use SMWOutputs;
22
use SMWQueryResult;
23
use Title;
24
25
/**
26
 * @licence GNU GPL v2+
27
 * @author Jeroen De Dauw < [email protected] >
28
 * @author Peter Grassberger < [email protected] >
29
 */
30
class MapPrinter extends ResultPrinter {
31
32
	private static $services = [];
33
34
	/**
35
	 * @var LocationParser
36
	 */
37
	private $locationParser;
38
39
	/**
40
	 * @var FileUrlFinder
41
	 */
42
	private $fileUrlFinder;
43
44
	/**
45
	 * @var MappingService
46
	 */
47
	private $service;
48
49
	/**
50
	 * @var ElementJsonSerializer
51
	 */
52
	private $elementSerializer;
53
54
	/**
55
	 * @var string|boolean
56
	 */
57
	private $fatalErrorMsg = false;
58
59
	/**
60
	 * @param string $format
61
	 * @param bool $inline
62
	 */
63 3
	public function __construct( $format, $inline = true ) {
64 3
		$this->service = self::$services[$format];
65
66 3
		parent::__construct( $format, $inline );
67 3
	}
68
69
	/**
70
	 * @since 3.4
71
	 * FIXME: this is a temporary hack that should be replaced when SMW allows for dependency
72
	 * injection in query printers.
73
	 *
74
	 * @param MappingService $service
75
	 */
76
	public static function registerService( MappingService $service ) {
77
		self::$services[$service->getName()] = $service;
78
	}
79
80
	public static function registerDefaultService( $serviceName ) {
81
		self::$services['map'] = self::$services[$serviceName];
82
	}
83
84 3
	private function getParser(): Parser {
85 3
		return MediaWikiServices::getInstance()->getParser();
86
	}
87
88 3
	private function getParserClone(): Parser {
89 3
		$parser = $this->getParser();
90 3
		return clone $parser;
91
	}
92
93
	/**
94
	 * Builds up and returns the HTML for the map, with the queried coordinate data on it.
95
	 *
96
	 * @param SMWQueryResult $res
97
	 * @param int $outputMode
98
	 *
99
	 * @return string
100
	 */
101 3
	public final function getResultText( SMWQueryResult $res, $outputMode ) {
102 3
		if ( $this->fatalErrorMsg !== false ) {
103
			return $this->fatalErrorMsg;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->fatalErrorMsg; of type string|boolean adds the type boolean to the return on line 103 which is incompatible with the return type documented by Maps\Map\SemanticFormat\MapPrinter::getResultText of type string.
Loading history...
104
		}
105
106 3
		$this->isHTML = true;
107
108 3
		$factory = \Maps\MapsFactory::globalInstance();
109 3
		$this->locationParser = $factory->newLocationParser();
110 3
		$this->fileUrlFinder = $factory->getFileUrlFinder();
111
112 3
		$this->elementSerializer = new ElementJsonSerializer( new WikitextParser( $this->getParserClone() ) );
113
114 3
		$this->addTrackingCategoryIfNeeded();
115
116 3
		$params = $this->params;
117
118 3
		$queryHandler = new QueryHandler( $res, $outputMode );
119 3
		$queryHandler->setLinkStyle( $params['link'] );
120 3
		$queryHandler->setHeaderStyle( $params['headers'] );
121 3
		$queryHandler->setShowSubject( $params['showtitle'] );
122 3
		$queryHandler->setTemplate( $params['template'] );
123 3
		$queryHandler->setUserParam( $params['userparam'] );
124 3
		$queryHandler->setHideNamespace( $params['hidenamespace'] );
125 3
		$queryHandler->setActiveIcon( $params['activeicon'] );
126
127 3
		$this->handleMarkerData( $params, $queryHandler );
128
129 3
		$params['lines'] = $this->elementsToJson( $params['lines'] );
130 3
		$params['polygons'] = $this->elementsToJson( $params['polygons'] );
131 3
		$params['circles'] = $this->elementsToJson( $params['circles'] );
132 3
		$params['rectangles'] = $this->elementsToJson( $params['rectangles'] );
133
134 3
		$params['ajaxquery'] = urlencode( $params['ajaxquery'] );
135
136 3
		if ( $params['locations'] === [] ) {
137
			return $params['default'];
138
		}
139
140
		// We can only take care of the zoom defaulting here,
141
		// as not all locations are available in whats passed to Validator.
142 3
		if ( $this->fullParams['zoom']->wasSetToDefault() && count( $params['locations'] ) > 1 ) {
143 3
			$params['zoom'] = false;
144
		}
145
146 3
		if ( array_key_exists( 'source', $params ) ) {
147 3
			unset( $params['source'] );
148
		}
149
150 3
		$outputBuilder = new MapOutputBuilder();
151 3
		$mapOutput = $outputBuilder->buildOutput( $this->service, $this->service->newMapDataFromParameters( $params ) );
152
153 3
		$this->outputResources( $mapOutput );
154
155 3
		return $mapOutput->getHtml();
156
	}
157
158 3
	private function outputResources( MapOutput $mapOutput ) {
159 3
		SMWOutputs::requireHeadItem(
160 3
			$this->randomString(),
161 3
			$mapOutput->getHeadItems()
162
		);
163
164 3
		foreach ( $mapOutput->getResourceModules() as $resourceModule ) {
165 3
			SMWOutputs::requireResource( $resourceModule );
166
		}
167 3
	}
168
169 3
	private function randomString(): string {
170 3
		return substr( str_shuffle( '0123456789abcdefghijklmnopqrstuvwxyz' ), 0, 10 );
171
	}
172
173 3
	private function elementsToJson( array $elements ) {
174 3
		return array_map(
175 3
			function( BaseElement $element ) {
176
				return $this->elementSerializer->elementToJson( $element );
177 3
			},
178
			$elements
179
		);
180
	}
181
182 3
	private function addTrackingCategoryIfNeeded() {
183 3
		$parser = MediaWikiServices::getInstance()->getParser();
184
185 3
		if ( $GLOBALS['egMapsEnableCategory'] && $parser->getOutput() !== null ) {
186
			$parser->addTrackingCategory( 'maps-tracking-category' );
187
		}
188 3
	}
189
190
	/**
191
	 * Converts the data in the coordinates parameter to JSON-ready objects.
192
	 * These get stored in the locations parameter, and the coordinates on gets deleted.
193
	 *
194
	 * @param array &$params
195
	 * @param \Maps\SemanticMW\QueryHandler $queryHandler
196
	 */
197 3
	private function handleMarkerData( array &$params, QueryHandler $queryHandler ) {
198 3
		$params['centre'] = $this->getCenter( $params['centre'] );
199
200 3
		$iconUrl = $this->fileUrlFinder->getUrlForFileName( $params['icon'] );
201 3
		$visitedIconUrl = $this->fileUrlFinder->getUrlForFileName( $params['visitedicon'] ?? '' );
202
203 3
		$params['locations'] = $this->getJsonForStaticLocations(
204 3
			$params['staticlocations'],
205
			$params,
206
			$iconUrl,
207
			$visitedIconUrl
208
		);
209
210 3
		unset( $params['staticlocations'] );
211
212 3
		$params['locations'] = array_merge(
213 3
			$params['locations'],
214 3
			$this->getJsonForLocations(
215 3
				$queryHandler->getLocations(),
216
				$params,
217
				$iconUrl,
218
				$visitedIconUrl
219
			)
220
		);
221 3
	}
222
223 3
	private function getCenter( $coordinatesOrAddress ) {
224 3
		if ( $coordinatesOrAddress === false ) {
225 3
			return false;
226
		}
227
228
		try {
229
			// FIXME: a Location makes no sense here, since the non-coordinate data is not used
230
			$location = $this->locationParser->parse( $coordinatesOrAddress );
231
		}
232
		catch ( \Exception $ex ) {
233
			// TODO: somehow report this to the user
234
			return false;
235
		}
236
237
		return $location->getJSONObject();
238
	}
239
240 3
	private function getJsonForStaticLocations( array $staticLocations, array $params, $iconUrl, $visitedIconUrl ) {
241 3
		$locationsJson = [];
242
243 3
		foreach ( $staticLocations as $location ) {
244
			$locationsJson[] = $this->getJsonForStaticLocation(
245
				$location,
246
				$params,
247
				$iconUrl,
248
				$visitedIconUrl
249
			);
250
		}
251
252 3
		return $locationsJson;
253
	}
254
255
	private function getJsonForStaticLocation( Location $location, array $params, $iconUrl, $visitedIconUrl ) {
256
		$jsonObj = $location->getJSONObject( $params['title'], $params['label'], $iconUrl, '', '', $visitedIconUrl );
257
258
		$this->elementSerializer->titleAndText( $jsonObj );
259
260
		if ( $params['pagelabel'] ) {
261
			$jsonObj['inlineLabel'] = Linker::link( Title::newFromText( $jsonObj['title'] ) );
262
		}
263
264
		return $jsonObj;
265
	}
266
267
	/**
268
	 * @param Location[] $locations
269
	 * @param array $params
270
	 * @param string $iconUrl
271
	 * @param string $visitedIconUrl
272
	 *
273
	 * @return array
274
	 */
275 3
	private function getJsonForLocations( iterable $locations, array $params, string $iconUrl, string $visitedIconUrl ): array {
276 3
		$locationsJson = [];
277
278 3
		foreach ( $locations as $location ) {
279 3
			$jsonObj = $location->getJSONObject(
280 3
				$params['title'],
281 3
				$params['label'],
282
				$iconUrl,
283 3
				'',
284 3
				'',
285
				$visitedIconUrl
286
			);
287
288 3
			$jsonObj['title'] = strip_tags( $jsonObj['title'] );
289
290 3
			$locationsJson[] = $jsonObj;
291
		}
292
293 3
		return $locationsJson;
294
	}
295
296
	/**
297
	 * Returns the internationalized name of the mapping service.
298
	 *
299
	 * @return string
300
	 */
301
	public final function getName() {
302
		return wfMessage( 'maps_' . $this->service->getName() )->text();
303
	}
304
305
	/**
306
	 * Returns a list of parameter information, for usage by Special:Ask and others.
307
	 *
308
	 * @return array
309
	 */
310 3
	public function getParameters() {
311 3
		$params = parent::getParameters();
312 3
		$paramInfo = $this->getParameterInfo();
313
314 3
		$params = array_merge( $params, $paramInfo );
315
316 3
		return $params;
317
	}
318
319
	/**
320
	 * Returns an array containing the parameter info.
321
	 *
322
	 * @return array
323
	 */
324 3
	private function getParameterInfo() {
325 3
		global $smgQPShowTitle, $smgQPTemplate, $smgQPHideNamespace;
326
327 3
		$params = $this->service->getParameterInfo();
328
329 3
		$params['staticlocations'] = [
330
			'type' => 'mapslocation',
331
			'aliases' => [ 'locations', 'points' ],
332
			'default' => [],
333
			'islist' => true,
334
			'delimiter' => ';',
335
			'message' => 'semanticmaps-par-staticlocations',
336
		];
337
338 3
		$params['showtitle'] = [
339 3
			'type' => 'boolean',
340 3
			'aliases' => 'show title',
341 3
			'default' => $smgQPShowTitle,
342
		];
343
344 3
		$params['hidenamespace'] = [
345 3
			'type' => 'boolean',
346 3
			'aliases' => 'hide namespace',
347 3
			'default' => $smgQPHideNamespace,
348
		];
349
350 3
		$params['template'] = [
351 3
			'default' => $smgQPTemplate,
352
		];
353
354 3
		$params['userparam'] = [
355
			'default' => '',
356
		];
357
358 3
		$params['activeicon'] = [
359
			'type' => 'string',
360
			'default' => '',
361
		];
362
363 3
		$params['pagelabel'] = [
364
			'type' => 'boolean',
365
			'default' => false,
366
		];
367
368 3
		$params['ajaxcoordproperty'] = [
369
			'default' => '',
370
		];
371
372 3
		$params['ajaxquery'] = [
373
			'default' => '',
374
			'type' => 'string'
375
		];
376
377
		// Messages:
378
		// semanticmaps-par-staticlocations, semanticmaps-par-showtitle, semanticmaps-par-hidenamespace,
379
		// semanticmaps-par-template, semanticmaps-par-userparam, semanticmaps-par-activeicon,
380
		// semanticmaps-par-pagelabel, semanticmaps-par-ajaxcoordproperty semanticmaps-par-ajaxquery
381 3
		foreach ( $params as $name => &$data ) {
382 3
			if ( is_array( $data ) && !array_key_exists( 'message', $data ) ) {
383 3
				$data['message'] = 'semanticmaps-par-' . $name;
384
			}
385
		}
386
387 3
		return $params;
388
	}
389
}
390