Completed
Push — rm-dc-stub ( 6678b8 )
by Jeroen De
04:04
created

SMMapPrinter::getMapHTML()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 16
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 11
nc 1
nop 2
crap 2
1
<?php
2
3
use Maps\Elements\Location;
4
use Maps\Element;
5
use Maps\Elements\BaseElement;
6
use Maps\LocationParser;
7
use ParamProcessor\ParamDefinition;
8
use ValueParsers\ParserOptions as ValueParserOptions;
9
10
/**
11
 * Query printer for maps. Is invoked via SMMapper.
12
 * Can be overridden per service to have custom output.
13
 *
14
 * @ingroup SemanticMaps
15
 *
16
 * @licence GNU GPL v2+
17
 * @author Jeroen De Dauw < [email protected] >
18
 * @author Peter Grassberger < [email protected] >
19
 */
20
class SMMapPrinter extends SMW\ResultPrinter {
21
22
	private static $services = [];
23
24
	/**
25
	 * @var LocationParser
26
	 */
27
	private $locationParser;
28
29
	/**
30
	 * @since 3.4
31
	 * FIXME: this is a temporary hack that should be replaced when SMW allows for dependency
32
	 * injection in query printers.
33
	 *
34
	 * @param MapsMappingService $service
35
	 */
36
	public static function registerService( MapsMappingService $service ) {
37
		self::$services[$service->getName()] = $service;
38
	}
39
40
	public static function registerDefaultService( $serviceName ) {
41
		self::$services['map'] = self::$services[$serviceName];
42
	}
43
	
44
	/**
45
	 * @var MapsMappingService
46
	 */
47
	private $service;
48
	
49
	/**
50
	 * @var string|boolean
51
	 */
52
	private $fatalErrorMsg = false;
53
	
54
	/**
55
	 * @param string $format
56
	 * @param bool $inline
57
	 */
58
	public function __construct( $format, $inline = true ) {
59
		$this->service = self::$services[$format];
60
		
61
		parent::__construct( $format, $inline );
62
	}
63
64
	/**
65
	 * Returns an array containing the parameter info.
66
	 * 
67
	 * @return array
68
	 */
69
	private function getParameterInfo() {
70
		global $smgQPShowTitle, $smgQPTemplate, $smgQPHideNamespace;
71
		
72
		$params = ParamDefinition::getCleanDefinitions( MapsMapper::getCommonParameters() );
0 ignored issues
show
Documentation introduced by
\MapsMapper::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...
73
74
		$this->service->addParameterInfo( $params );
75
76
		$params['staticlocations'] = [
77
			'type' => 'mapslocation', // FIXME: geoservice is not used
78
			'aliases' => [ 'locations', 'points' ],
79
			'default' => [],
80
			'islist' => true,
81
			'delimiter' => ';',
82
			'message' => 'semanticmaps-par-staticlocations',
83
		];
84
85
		$params['showtitle'] = [
86
			'type' => 'boolean',
87
			'aliases' => 'show title',
88
			'default' => $smgQPShowTitle,
89
		];
90
91
		$params['hidenamespace'] = [
92
			'type' => 'boolean',
93
			'aliases' => 'hide namespace',
94
			'default' => $smgQPHideNamespace,
95
		];
96
97
		$params['template'] = [
98
			'default' => $smgQPTemplate,
99
		];
100
101
		$params['userparam'] = [
102
			'default' => '',
103
		];
104
105
		$params['activeicon'] =  [
106
			'type' => 'string',
107
			'default' => '',
108
		];
109
110
		$params['pagelabel'] =  [
111
			'type' => 'boolean',
112
			'default' => false,
113
		];
114
115
		$params['ajaxcoordproperty'] = array(
116
			'default' => '',
117
		);
118
119
		$params['ajaxquery'] = array(
120
			'default' => '',
121
			'type' => 'string'
122
		);
123
124
		// Messages:
125
		// semanticmaps-par-staticlocations, semanticmaps-par-showtitle, semanticmaps-par-hidenamespace,
126
		// semanticmaps-par-template, semanticmaps-par-userparam, semanticmaps-par-activeicon,
127
		// semanticmaps-par-pagelabel, semanticmaps-par-ajaxcoordproperty semanticmaps-par-ajaxquery
128
		foreach ( $params as $name => &$data ) {
129
			if ( is_array( $data ) && !array_key_exists( 'message', $data ) ) {
130
				$data['message'] = 'semanticmaps-par-' . $name;
131
			}
132
		}
133
134
		return $params;
135
	}
136
	
137
	/**
138
	 * Builds up and returns the HTML for the map, with the queried coordinate data on it.
139
	 *
140
	 * @param SMWQueryResult $res
141
	 * @param $outputmode
142
	 * 
143
	 * @return string
144
	 */
145
	public final function getResultText( SMWQueryResult $res, $outputmode ) {
146
		if ( $this->fatalErrorMsg !== false ) {
147
			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 147 which is incompatible with the return type documented by SMMapPrinter::getResultText of type string.
Loading history...
148
		}
149
150
		$this->addTrackingCategoryIfNeeded();
151
152
		$params = $this->params;
153
154
		$this->initializeLocationParser( $params );
155
156
		$queryHandler = new SMQueryHandler( $res, $outputmode );
157
		$queryHandler->setLinkStyle( $params['link'] );
158
		$queryHandler->setHeaderStyle($params['headers']);
159
		$queryHandler->setShowSubject( $params['showtitle'] );
160
		$queryHandler->setTemplate( $params['template'] );
161
		$queryHandler->setUserParam( $params['userparam'] );
162
		$queryHandler->setHideNamespace( $params['hidenamespace'] );
163
		$queryHandler->setActiveIcon( $params['activeicon'] );
164
165
		$this->handleMarkerData( $params, $queryHandler );
166
167
		$params['ajaxquery'] = urlencode( $params['ajaxquery'] );
168
169
		$locationAmount = count( $params['locations'] );
170
171
		if ( $locationAmount > 0 ) {
172
			// We can only take care of the zoom defaulting here,
173
			// as not all locations are available in whats passed to Validator.
174
			if ( $this->fullParams['zoom']->wasSetToDefault() && $locationAmount > 1 ) {
175
				$params['zoom'] = false;
176
			}
177
178
			$mapName = $this->service->getMapId();
179
180
			SMWOutputs::requireHeadItem(
181
				$mapName,
182
				$this->service->getDependencyHtml() .
183
				$configVars = Skin::makeVariablesScript( $this->service->getConfigVariables() )
184
			);
185
186
			foreach ( $this->service->getResourceModules() as $resourceModule ) {
187
				SMWOutputs::requireResource( $resourceModule );
188
			}
189
190
			if ( array_key_exists( 'source', $params ) ) {
191
				unset( $params['source'] );
192
			}
193
194
			return $this->getMapHTML( $params, $mapName );
195
		}
196
		else {
197
			return $params['default'];
198
		}
199
	}
200
201
	private function addTrackingCategoryIfNeeded() {
0 ignored issues
show
Coding Style introduced by
addTrackingCategoryIfNeeded uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
202
		/**
203
		 * @var Parser $wgParser
204
		 */
205
		global $wgParser;
206
207
		if ( $GLOBALS['egMapsEnableCategory'] && $wgParser->getOutput() !== null ) {
208
			$wgParser->addTrackingCategory( 'maps-tracking-category' );
209
		}
210
	}
211
212
	private function initializeLocationParser( array $params ) {
213
		$this->locationParser = new LocationParser( new ValueParserOptions( [
214
			'geoService' => $params['geoservice']
215
		] ) );
216
	}
217
218
	/**
219
	 * Converts the data in the coordinates parameter to JSON-ready objects.
220
	 * These get stored in the locations parameter, and the coordinates on gets deleted.
221
	 *
222
	 * @param array &$params
223
	 * @param SMQueryHandler $queryHandler
224
	 */
225
	private function handleMarkerData( array &$params, SMQueryHandler $queryHandler ) {
226
		$params['centre'] = $this->getCenter( $params['centre'] );
227
228
		$iconUrl = MapsMapper::getFileUrl( $params['icon'] );
0 ignored issues
show
Deprecated Code introduced by
The method MapsMapper::getFileUrl() has been deprecated.

This method has been deprecated.

Loading history...
229
		$visitedIconUrl = MapsMapper::getFileUrl( $params['visitedicon'] );
0 ignored issues
show
Deprecated Code introduced by
The method MapsMapper::getFileUrl() has been deprecated.

This method has been deprecated.

Loading history...
230
231
		$params['locations'] = $this->getJsonForStaticLocations(
232
			$params['staticlocations'],
233
			$params,
234
			$iconUrl,
235
			$visitedIconUrl
236
		);
237
238
		unset( $params['staticlocations'] );
239
240
		$this->addShapeData( $queryHandler->getShapes(), $params, $iconUrl, $visitedIconUrl );
241
242
		if ( $params['format'] === 'openlayers' ) {
243
			$params['layers'] = MapsDisplayMapRenderer::evilOpenLayersHack( $params['layers'] );
0 ignored issues
show
Deprecated Code introduced by
The method MapsDisplayMapRenderer::evilOpenLayersHack() has been deprecated.

This method has been deprecated.

Loading history...
244
		}
245
	}
246
247
	private function getCenter( $coordinatesOrAddress ) {
248
		if ( $coordinatesOrAddress === false ) {
249
			return false;
250
		}
251
252
		try {
253
			// FIXME: a Location makes no sense here, since the non-coordinate data is not used
254
			$location = $this->locationParser->stringParse( $coordinatesOrAddress );
255
		}
256
		catch ( \Exception $ex ) {
257
			// TODO: somehow report this to the user
258
			return false;
259
		}
260
261
		return $location->getJSONObject();
262
	}
263
264
	/**
265
	 * Returns the HTML to display the map.
266
	 *
267
	 * @param array $params
268
	 * @param string $mapName
269
	 *
270
	 * @return string
271
	 */
272
	private function getMapHTML( array $params, $mapName ) {
273
		return Html::rawElement(
274
			'div',
275
			[
276
				'id' => $mapName,
277
				'style' => "width: {$params['width']}; height: {$params['height']}; background-color: #cccccc; overflow: hidden;",
278
				'class' => 'maps-map maps-' . $this->service->getName()
279
			],
280
			wfMessage( 'maps-loading-map' )->inContentLanguage()->escaped() .
281
				Html::element(
282
					'div',
283
					[ 'style' => 'display:none', 'class' => 'mapdata' ],
284
					FormatJson::encode( $params )
285
				)
286
		);
287
	}
288
289
	private function getJsonForStaticLocations( array $staticLocations, array $params, $iconUrl, $visitedIconUrl ) {
0 ignored issues
show
Coding Style introduced by
getJsonForStaticLocations uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
290
		/**
291
		 * @var Parser $wgParser
292
		 */
293
		global $wgParser;
294
295
		$parser = version_compare( $GLOBALS['wgVersion'], '1.18', '<' ) ? $wgParser : clone $wgParser;
296
297
		$locationsJson = [];
298
299
		foreach ( $staticLocations as $location ) {
300
			$locationsJson[] = $this->getJsonForStaticLocation(
301
				$location,
302
				$params,
303
				$iconUrl,
304
				$visitedIconUrl,
305
				$parser
306
			);
307
		}
308
309
		return $locationsJson;
310
	}
311
312
	private function getJsonForStaticLocation( Location $location, array $params, $iconUrl, $visitedIconUrl, Parser $parser ) {
313
		$jsonObj = $location->getJSONObject( $params['title'], $params['label'], $iconUrl, '', '', $visitedIconUrl );
314
315
		$jsonObj['title'] = $parser->parse( $jsonObj['title'], $parser->getTitle(), new ParserOptions() )->getText();
316
		$jsonObj['text'] = $parser->parse( $jsonObj['text'], $parser->getTitle(), new ParserOptions() )->getText();
317
318
		$hasTitleAndtext = $jsonObj['title'] !== '' && $jsonObj['text'] !== '';
319
		$jsonObj['text'] = ( $hasTitleAndtext ? '<b>' . $jsonObj['title'] . '</b><hr />' : $jsonObj['title'] ) . $jsonObj['text'];
320
		$jsonObj['title'] = strip_tags( $jsonObj['title'] );
321
322
		if ( $params['pagelabel'] ) {
323
			$jsonObj['inlineLabel'] = Linker::link( Title::newFromText( $jsonObj['title'] ) );
324
		}
325
326
		return $jsonObj;
327
	}
328
329
	/**
330
	 * @param Element[] $queryShapes
331
	 * @param array $params
332
	 * @param string $iconUrl
333
	 * @param string $visitedIconUrl
334
	 */
335
	private function addShapeData( array $queryShapes, array &$params, $iconUrl, $visitedIconUrl ) {
336
		$params['locations'] = array_merge(
337
			$params['locations'],
338
			$this->getJsonForLocations(
339
				$queryShapes['locations'],
340
				$params,
341
				$iconUrl,
342
				$visitedIconUrl
343
			)
344
		);
345
346
		$params['lines'] = $this->getElementJsonArray( $queryShapes['lines'], $params );
347
		$params['polygons'] = $this->getElementJsonArray( $queryShapes['polygons'], $params );
348
	}
349
350
	/**
351
	 * @param Location[] $locations
352
	 * @param array $params
353
	 * @param string $iconUrl
354
	 * @param string $visitedIconUrl
355
	 *
356
	 * @return array
357
	 */
358
	private function getJsonForLocations( array $locations, array $params, $iconUrl, $visitedIconUrl ) {
359
		$locationsJson = [];
360
361
		foreach ( $locations as $location ) {
362
			$jsonObj = $location->getJSONObject( $params['title'], $params['label'], $iconUrl, '', '', $visitedIconUrl );
363
364
			$jsonObj['title'] = strip_tags( $jsonObj['title'] );
365
366
			$locationsJson[] = $jsonObj;
367
		}
368
369
		return $locationsJson;
370
	}
371
372
	/**
373
	 * @param BaseElement[] $elements
374
	 * @param array $params
375
	 *
376
	 * @return array
377
	 */
378
	private function getElementJsonArray( array $elements, array $params ) {
379
		$elementsJson = [];
380
381
		foreach ( $elements as $element ) {
382
			$jsonObj = $element->getJSONObject( $params['title'], $params['label'] );
0 ignored issues
show
Deprecated Code introduced by
The method Maps\Elements\BaseElement::getJSONObject() has been deprecated.

This method has been deprecated.

Loading history...
383
			$elementsJson[] = $jsonObj;
384
		}
385
386
		return $elementsJson;
387
	}
388
389
	/**
390
	 * Returns the internationalized name of the mapping service.
391
	 * 
392
	 * @return string
393
	 */
394
	public final function getName() {
395
		return wfMessage( 'maps_' . $this->service->getName() )->text();
396
	}
397
	
398
	/**
399
	 * Returns a list of parameter information, for usage by Special:Ask and others.
400
	 * 
401
	 * @return array
402
	 */
403
    public function getParameters() {
404
        $params = parent::getParameters();
405
        $paramInfo = $this->getParameterInfo();
406
        
407
        // Do not display this as an option, as the format already determines it
408
        // TODO: this can probably be done cleaner with some changes in Maps
409
        unset( $paramInfo['mappingservice'] );
410
        
411
        $params = array_merge( $params, $paramInfo );
412
413
		return $params;
414
    }
415
}
416