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