Completed
Push — master ( 387358...064f37 )
by Jeroen De
03:22
created

MapPrinter   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Test Coverage

Coverage 82.98%

Importance

Changes 0
Metric Value
wmc 34
lcom 2
cbo 10
dl 0
loc 366
ccs 117
cts 141
cp 0.8298
rs 9.68
c 0
b 0
f 0

18 Methods

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