Completed
Push — master ( 21f028...112b84 )
by Jeroen De
03:13
created

MapPrinter::elementsToJson()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 8
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 FormatJson;
6
use Html;
7
use Linker;
8
use Maps\Elements\BaseElement;
9
use Maps\Elements\Line;
10
use Maps\Elements\Location;
11
use Maps\FileUrlFinder;
12
use Maps\MappingService;
13
use Maps\MapsFunctions;
14
use Maps\MediaWiki\ParserHooks\DisplayMapRenderer;
15
use Maps\Presentation\ElementJsonSerializer;
16
use Maps\Presentation\WikitextParser;
17
use Maps\Presentation\WikitextParsers\LocationParser;
18
use ParamProcessor\ParamDefinition;
19
use Parser;
20
use ParserOptions;
21
use SMW;
22
use SMWOutputs;
23
use SMWQueryResult;
24
use Title;
25
26
/**
27
 * @licence GNU GPL v2+
28
 * @author Jeroen De Dauw < [email protected] >
29
 * @author Peter Grassberger < [email protected] >
30
 */
31
class MapPrinter extends SMW\ResultPrinter {
32
33
	private static $services = [];
34
35
	/**
36
	 * @var LocationParser
37
	 */
38
	private $locationParser;
39
40
	/**
41
	 * @var FileUrlFinder
42
	 */
43
	private $fileUrlFinder;
44
45
	/**
46
	 * @var MappingService
47
	 */
48
	private $service;
49
50
	/**
51
	 * @var WikitextParser
52
	 */
53
	private $wikitextParser;
54
55
	/**
56
	 * @var ElementJsonSerializer
57
	 */
58
	private $elementSerializer;
59
60
	/**
61
	 * @var string|boolean
62
	 */
63
	private $fatalErrorMsg = false;
64
65
	/**
66
	 * @param string $format
67
	 * @param bool $inline
68
	 */
69
	public function __construct( $format, $inline = true ) {
70
		$this->service = self::$services[$format];
71
72
		parent::__construct( $format, $inline );
73
	}
74
75
	/**
76
	 * @since 3.4
77
	 * FIXME: this is a temporary hack that should be replaced when SMW allows for dependency
78
	 * injection in query printers.
79
	 *
80
	 * @param MappingService $service
81
	 */
82
	public static function registerService( MappingService $service ) {
83
		self::$services[$service->getName()] = $service;
84
	}
85
86
	public static function registerDefaultService( $serviceName ) {
87
		self::$services['map'] = self::$services[$serviceName];
88
	}
89
90
	/**
91
	 * Builds up and returns the HTML for the map, with the queried coordinate data on it.
92
	 *
93
	 * @param SMWQueryResult $res
94
	 * @param int $outputMode
95
	 *
96
	 * @return string
97
	 */
98
	public final function getResultText( SMWQueryResult $res, $outputMode ) {
99
		if ( $this->fatalErrorMsg !== false ) {
100
			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 100 which is incompatible with the return type documented by Maps\SemanticMW\ResultPr...pPrinter::getResultText of type string.
Loading history...
101
		}
102
103
		$factory = \Maps\MapsFactory::newDefault();
104
		$this->locationParser = $factory->newLocationParser();
105
		$this->fileUrlFinder = $factory->getFileUrlFinder();
106
107
		$this->wikitextParser = new WikitextParser( clone $GLOBALS['wgParser'] );
108
		$this->elementSerializer = new ElementJsonSerializer( $this->wikitextParser );
109
110
		$this->addTrackingCategoryIfNeeded();
111
112
		$params = $this->params;
113
114
		$queryHandler = new QueryHandler( $res, $outputMode );
115
		$queryHandler->setLinkStyle( $params['link'] );
116
		$queryHandler->setHeaderStyle( $params['headers'] );
117
		$queryHandler->setShowSubject( $params['showtitle'] );
118
		$queryHandler->setTemplate( $params['template'] );
119
		$queryHandler->setUserParam( $params['userparam'] );
120
		$queryHandler->setHideNamespace( $params['hidenamespace'] );
121
		$queryHandler->setActiveIcon( $params['activeicon'] );
122
123
		$this->handleMarkerData( $params, $queryHandler );
124
125
		$params['lines'] = $this->elementsToJson( $params['lines'] );
126
127
		$params['ajaxquery'] = urlencode( $params['ajaxquery'] );
128
129
		$this->service->addHtmlDependencies(
130
			DisplayMapRenderer::getLayerDependencies( $params['format'], $params )
131
		);
132
133
		if ( $params['locations'] === [] ) {
134
			return $params['default'];
135
		}
136
137
		// We can only take care of the zoom defaulting here,
138
		// as not all locations are available in whats passed to Validator.
139
		if ( $this->fullParams['zoom']->wasSetToDefault() && count( $params['locations'] ) > 1 ) {
140
			$params['zoom'] = false;
141
		}
142
143
		$mapName = $this->service->getMapId();
144
145
		SMWOutputs::requireHeadItem(
146
			$mapName,
147
			$this->service->getDependencyHtml()
148
		);
149
150
		foreach ( $this->service->getResourceModules() as $resourceModule ) {
151
			SMWOutputs::requireResource( $resourceModule );
152
		}
153
154
		if ( array_key_exists( 'source', $params ) ) {
155
			unset( $params['source'] );
156
		}
157
158
		return $this->getMapHTML( $params, $mapName );
159
	}
160
161
	private function elementsToJson( array $elements ) {
162
		return array_map(
163
			function( BaseElement $element ) {
164
				return $this->elementSerializer->elementToJson( $element );
165
			},
166
			$elements
167
		);
168
	}
169
170
	private function addTrackingCategoryIfNeeded() {
171
		/**
172
		 * @var Parser $wgParser
173
		 */
174
		global $wgParser;
175
176
		if ( $GLOBALS['egMapsEnableCategory'] && $wgParser->getOutput() !== null ) {
177
			$wgParser->addTrackingCategory( 'maps-tracking-category' );
178
		}
179
	}
180
181
	/**
182
	 * Converts the data in the coordinates parameter to JSON-ready objects.
183
	 * These get stored in the locations parameter, and the coordinates on gets deleted.
184
	 *
185
	 * @param array &$params
186
	 * @param QueryHandler $queryHandler
187
	 */
188
	private function handleMarkerData( array &$params, QueryHandler $queryHandler ) {
189
		$params['centre'] = $this->getCenter( $params['centre'] );
190
191
		$iconUrl = $this->fileUrlFinder->getUrlForFileName( $params['icon'] );
192
		$visitedIconUrl = $this->fileUrlFinder->getUrlForFileName( $params['visitedicon'] );
193
194
		$params['locations'] = $this->getJsonForStaticLocations(
195
			$params['staticlocations'],
196
			$params,
197
			$iconUrl,
198
			$visitedIconUrl
199
		);
200
201
		unset( $params['staticlocations'] );
202
203
		$params['locations'] = array_merge(
204
			$params['locations'],
205
			$this->getJsonForLocations(
206
				$queryHandler->getLocations(),
207
				$params,
208
				$iconUrl,
209
				$visitedIconUrl
210
			)
211
		);
212
213
		if ( $params['format'] === 'openlayers' ) {
214
			$params['layers'] = DisplayMapRenderer::evilOpenLayersHack( $params['layers'] );
0 ignored issues
show
Deprecated Code introduced by
The method Maps\MediaWiki\ParserHoo...r::evilOpenLayersHack() has been deprecated.

This method has been deprecated.

Loading history...
215
		}
216
	}
217
218
	private function getCenter( $coordinatesOrAddress ) {
219
		if ( $coordinatesOrAddress === false ) {
220
			return false;
221
		}
222
223
		try {
224
			// FIXME: a Location makes no sense here, since the non-coordinate data is not used
225
			$location = $this->locationParser->parse( $coordinatesOrAddress );
226
		}
227
		catch ( \Exception $ex ) {
228
			// TODO: somehow report this to the user
229
			return false;
230
		}
231
232
		return $location->getJSONObject();
233
	}
234
235
	private function getJsonForStaticLocations( array $staticLocations, array $params, $iconUrl, $visitedIconUrl ) {
236
		$locationsJson = [];
237
238
		foreach ( $staticLocations as $location ) {
239
			$locationsJson[] = $this->getJsonForStaticLocation(
240
				$location,
241
				$params,
242
				$iconUrl,
243
				$visitedIconUrl,
244
				clone $GLOBALS['wgParser']
245
			);
246
		}
247
248
		return $locationsJson;
249
	}
250
251
	private function getJsonForStaticLocation( Location $location, array $params, $iconUrl, $visitedIconUrl, Parser $parser ) {
252
		$jsonObj = $location->getJSONObject( $params['title'], $params['label'], $iconUrl, '', '', $visitedIconUrl );
253
254
		$jsonObj['title'] = $parser->parse( $jsonObj['title'], $parser->getTitle(), new ParserOptions() )->getText();
255
		$jsonObj['text'] = $parser->parse( $jsonObj['text'], $parser->getTitle(), new ParserOptions() )->getText();
256
257
		$hasTitleAndtext = $jsonObj['title'] !== '' && $jsonObj['text'] !== '';
258
		$jsonObj['text'] = ( $hasTitleAndtext ? '<b>' . $jsonObj['title'] . '</b><hr />' : $jsonObj['title'] ) . $jsonObj['text'];
259
		$jsonObj['title'] = strip_tags( $jsonObj['title'] );
260
261
		if ( $params['pagelabel'] ) {
262
			$jsonObj['inlineLabel'] = Linker::link( Title::newFromText( $jsonObj['title'] ) );
263
		}
264
265
		return $jsonObj;
266
	}
267
268
	/**
269
	 * @param Location[] $locations
270
	 * @param array $params
271
	 * @param string $iconUrl
272
	 * @param string $visitedIconUrl
273
	 *
274
	 * @return array
275
	 */
276
	private function getJsonForLocations( iterable $locations, array $params, string $iconUrl, string $visitedIconUrl ): array {
277
		$locationsJson = [];
278
279
		foreach ( $locations as $location ) {
280
			$jsonObj = $location->getJSONObject(
281
				$params['title'],
282
				$params['label'],
283
				$iconUrl,
284
				'',
285
				'',
286
				$visitedIconUrl
287
			);
288
289
			$jsonObj['title'] = strip_tags( $jsonObj['title'] );
290
291
			$locationsJson[] = $jsonObj;
292
		}
293
294
		return $locationsJson;
295
	}
296
297
	/**
298
	 * Returns the HTML to display the map.
299
	 *
300
	 * @param array $params
301
	 * @param string $mapName
302
	 *
303
	 * @return string
304
	 */
305
	private function getMapHTML( array $params, string $mapName ): string {
306
		return Html::rawElement(
307
			'div',
308
			[
309
				'id' => $mapName,
310
				'style' => "width: {$params['width']}; height: {$params['height']}; background-color: #cccccc; overflow: hidden;",
311
				'class' => 'maps-map maps-' . $this->service->getName()
312
			],
313
			wfMessage( 'maps-loading-map' )->inContentLanguage()->escaped() .
314
			Html::element(
315
				'div',
316
				[ 'style' => 'display:none', 'class' => 'mapdata' ],
317
				FormatJson::encode( $params )
318
			)
319
		);
320
	}
321
322
	/**
323
	 * Returns the internationalized name of the mapping service.
324
	 *
325
	 * @return string
326
	 */
327
	public final function getName() {
328
		return wfMessage( 'maps_' . $this->service->getName() )->text();
329
	}
330
331
	/**
332
	 * Returns a list of parameter information, for usage by Special:Ask and others.
333
	 *
334
	 * @return array
335
	 */
336
	public function getParameters() {
337
		$params = parent::getParameters();
338
		$paramInfo = $this->getParameterInfo();
339
340
		// Do not display this as an option, as the format already determines it
341
		// TODO: this can probably be done cleaner with some changes in Maps
342
		unset( $paramInfo['mappingservice'] );
343
344
		$params = array_merge( $params, $paramInfo );
345
346
		return $params;
347
	}
348
349
	/**
350
	 * Returns an array containing the parameter info.
351
	 *
352
	 * @return array
353
	 */
354
	private function getParameterInfo() {
355
		global $smgQPShowTitle, $smgQPTemplate, $smgQPHideNamespace;
356
357
		$params = ParamDefinition::getCleanDefinitions( MapsFunctions::getCommonParameters() );
0 ignored issues
show
Documentation introduced by
\Maps\MapsFunctions::getCommonParameters() is of type array<string,array<strin...efault\":\"false\"}>"}>, but the function expects a array<integer,object<Par...ssor\IParamDefinition>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
358
359
		$this->service->addParameterInfo( $params );
360
361
		$params['staticlocations'] = [
362
			'type' => 'mapslocation',
363
			'aliases' => [ 'locations', 'points' ],
364
			'default' => [],
365
			'islist' => true,
366
			'delimiter' => ';',
367
			'message' => 'semanticmaps-par-staticlocations',
368
		];
369
370
		$params['showtitle'] = [
371
			'type' => 'boolean',
372
			'aliases' => 'show title',
373
			'default' => $smgQPShowTitle,
374
		];
375
376
		$params['hidenamespace'] = [
377
			'type' => 'boolean',
378
			'aliases' => 'hide namespace',
379
			'default' => $smgQPHideNamespace,
380
		];
381
382
		$params['template'] = [
383
			'default' => $smgQPTemplate,
384
		];
385
386
		$params['userparam'] = [
387
			'default' => '',
388
		];
389
390
		$params['activeicon'] = [
391
			'type' => 'string',
392
			'default' => '',
393
		];
394
395
		$params['pagelabel'] = [
396
			'type' => 'boolean',
397
			'default' => false,
398
		];
399
400
		$params['ajaxcoordproperty'] = [
401
			'default' => '',
402
		];
403
404
		$params['ajaxquery'] = [
405
			'default' => '',
406
			'type' => 'string'
407
		];
408
409
		// Messages:
410
		// semanticmaps-par-staticlocations, semanticmaps-par-showtitle, semanticmaps-par-hidenamespace,
411
		// semanticmaps-par-template, semanticmaps-par-userparam, semanticmaps-par-activeicon,
412
		// semanticmaps-par-pagelabel, semanticmaps-par-ajaxcoordproperty semanticmaps-par-ajaxquery
413
		foreach ( $params as $name => &$data ) {
414
			if ( is_array( $data ) && !array_key_exists( 'message', $data ) ) {
415
				$data['message'] = 'semanticmaps-par-' . $name;
416
			}
417
		}
418
419
		return $params;
420
	}
421
}
422