Completed
Push — master ( e2ac37...21f028 )
by Jeroen De
03:38
created

DisplayMapRenderer   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 70.73%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 6
dl 0
loc 274
ccs 87
cts 123
cp 0.7073
rs 9.2
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A renderMap() 0 24 1
A handleMarkerData() 0 17 3
A getCenter() 0 16 3
A getLocationJson() 0 25 3
A getLocationJsonObject() 0 14 2
A handleShapeData() 0 17 4
B evilOpenLayersHack() 0 36 9
A getMapHTML() 0 16 1
C getLayerDependencies() 0 38 13

How to fix   Complexity   

Complex Class

Complex classes like DisplayMapRenderer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DisplayMapRenderer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Maps\MediaWiki\ParserHooks;
4
5
use FormatJson;
6
use Html;
7
use Maps\DataAccess\MediaWikiFileUrlFinder;
8
use Maps\Elements\Location;
9
use Maps\MappingService;
10
use Maps\Presentation\ElementJsonSerializer;
11
use Maps\Presentation\WikitextParser;
12
use Maps\Presentation\WikitextParsers\LocationParser;
13
use Parser;
14
15
/**
16
 * Class handling the #display_map rendering.
17
 *
18
 * @licence GNU GPL v2+
19
 * @author Jeroen De Dauw < [email protected] >
20
 * @author Kim Eik
21
 */
22
class DisplayMapRenderer {
23
24
	public $service;
25
26
	/**
27
	 * @var LocationParser
28
	 */
29
	private $locationParser;
30
31
	/**
32
	 * @var MediaWikiFileUrlFinder
33
	 */
34
	private $fileUrlFinder;
35
36
	/**
37
	 * @var WikitextParser
38
	 */
39
	private $wikitextParser;
40
	/**
41
	 * @var ElementJsonSerializer
42
	 */
43
	private $elementSerializer;
44
45 20
	public function __construct( MappingService $service = null ) {
46 20
		$this->service = $service;
47 20
	}
48
49
	/**
50
	 * Handles the request from the parser hook by doing the work that's common for all
51
	 * mapping services, calling the specific methods and finally returning the resulting output.
52
	 *
53
	 * @param array $params
54
	 * @param Parser $parser
55
	 *
56
	 * @return string
57
	 */
58 20
	public final function renderMap( array $params, Parser $parser ) {
59 20
		$factory = \Maps\MapsFactory::newDefault();
60
61 20
		$this->locationParser = $factory->newLocationParser();
62 20
		$this->fileUrlFinder = $factory->getFileUrlFinder();
63
64 20
		$this->wikitextParser = new WikitextParser( clone $parser );
65 20
		$this->elementSerializer = new ElementJsonSerializer( $this->wikitextParser );
66
67 20
		$this->handleMarkerData( $params );
68
69 20
		$output = $this->getMapHTML(
70 20
			$params,
71 20
			$this->service->getMapId()
72
		);
73
74 20
		$this->service->addHtmlDependencies(
75 20
			self::getLayerDependencies( $params['mappingservice'], $params )
76
		);
77
78 20
		$this->service->addDependencies( $parser->getOutput() );
79
80 20
		return $output;
81
	}
82
83
	/**
84
	 * Converts the data in the coordinates parameter to JSON-ready objects.
85
	 * These get stored in the locations parameter, and the coordinates on gets deleted.
86
	 */
87 20
	private function handleMarkerData( array &$params ) {
88 20
		$params['centre'] = $this->getCenter( $params['centre'] );
89
90 20
		if ( is_object( $params['wmsoverlay'] ) ) {
91
			$params['wmsoverlay'] = $params['wmsoverlay']->getJSONObject();
92
		}
93
94 20
		$params['locations'] = $this->getLocationJson( $params );
95
96 20
		unset( $params['coordinates'] );
97
98 20
		$this->handleShapeData( $params );
99
100 20
		if ( $params['mappingservice'] === 'openlayers' ) {
101
			$params['layers'] = self::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...
102
		}
103 20
	}
104
105 20
	private function getCenter( $coordinatesOrAddress ) {
106 20
		if ( $coordinatesOrAddress === false ) {
107 20
			return false;
108
		}
109
110
		try {
111
			// FIXME: a Location makes no sense here, since the non-coordinate data is not used
112
			$location = $this->locationParser->parse( $coordinatesOrAddress );
113
		}
114
		catch ( \Exception $ex ) {
115
			// TODO: somehow report this to the user
116
			return false;
117
		}
118
119
		return $location->getJSONObject();
120
	}
121
122 20
	private function getLocationJson( array $params ) {
123 20
		$iconUrl = $this->fileUrlFinder->getUrlForFileName( $params['icon'] );
124 20
		$visitedIconUrl = $this->fileUrlFinder->getUrlForFileName( $params['visitedicon'] );
125
126 20
		$locationJsonObjects = [];
127
128 20
		foreach ( $params['coordinates'] as $coordinatesOrAddress ) {
129
			try {
130 15
				$location = $this->locationParser->parse( $coordinatesOrAddress );
131
			}
132 1
			catch ( \Exception $ex ) {
133
				// TODO: somehow report this to the user
134 1
				continue;
135
			}
136
137 14
			$locationJsonObjects[] = $this->getLocationJsonObject(
138 14
				$location,
139 14
				$params,
140 14
				$iconUrl,
141 14
				$visitedIconUrl
142
			);
143
		}
144
145 20
		return $locationJsonObjects;
146
	}
147
148 14
	private function getLocationJsonObject( Location $location, array $params, $iconUrl, $visitedIconUrl ) {
149 14
		$jsonObj = $location->getJSONObject( $params['title'], $params['label'], $iconUrl, '', '', $visitedIconUrl );
150
151 14
		$this->elementSerializer->titleAndText( $jsonObj );
152
153 14
		if ( isset( $jsonObj['inlineLabel'] ) ) {
154 1
			$jsonObj['inlineLabel'] = strip_tags(
155 1
				$this->wikitextParser->wikitextToHtml( $jsonObj['inlineLabel'] ),
156 1
				'<a><img>'
157
			);
158
		}
159
160 14
		return $jsonObj;
161
	}
162
163 20
	private function handleShapeData( array &$params ) {
164
		$textContainers = [
165 20
			&$params['lines'],
166 20
			&$params['polygons'],
167 20
			&$params['circles'],
168 20
			&$params['rectangles'],
169 20
			&$params['imageoverlays'], // FIXME: this is Google Maps specific!!
170
		];
171
172 20
		foreach ( $textContainers as &$textContainer ) {
173 20
			if ( is_array( $textContainer ) ) {
174 20
				foreach ( $textContainer as &$obj ) {
175 20
					$obj = $this->elementSerializer->wikitextToHtml( $obj );
176
				}
177
			}
178
		}
179 20
	}
180
181
	/**
182
	 * FIXME
183
	 *
184
	 * Temporary hack until the mapping service handling gets a proper refactor
185
	 * This kind of JS construction is also rather evil and should not be done at this point
186
	 *
187
	 * @since 3.0
188
	 * @deprecated
189
	 *
190
	 * @param string[] $layers
191
	 *
192
	 * @return string[]
193
	 */
194
	public static function evilOpenLayersHack( $layers ) {
195
		global $egMapsOLLayerGroups, $egMapsOLAvailableLayers;
196
197
		$layerDefs = [];
198
		$layerNames = [];
199
200
		foreach ( $layers as $layerOrGroup ) {
201
			$lcLayerOrGroup = strtolower( $layerOrGroup );
202
203
			// Layer groups. Loop over all items and add them if not present yet:
204
			if ( array_key_exists( $lcLayerOrGroup, $egMapsOLLayerGroups ) ) {
205
				foreach ( $egMapsOLLayerGroups[$lcLayerOrGroup] as $layerName ) {
206
					if ( !in_array( $layerName, $layerNames ) ) {
207
						if ( is_array( $egMapsOLAvailableLayers[$layerName] ) ) {
208
							$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$layerName][0];
209
						} else {
210
							$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$layerName];
211
						}
212
						$layerNames[] = $layerName;
213
					}
214
				}
215
			} // Single layers. Add them if not present yet:
216
			elseif ( array_key_exists( $lcLayerOrGroup, $egMapsOLAvailableLayers ) ) {
217
				if ( !in_array( $lcLayerOrGroup, $layerNames ) ) {
218
					if ( is_array( $egMapsOLAvailableLayers[$lcLayerOrGroup] ) ) {
219
						$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$lcLayerOrGroup][0];
220
					} else {
221
						$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$lcLayerOrGroup];
222
					}
223
224
					$layerNames[] = $lcLayerOrGroup;
225
				}
226
			}
227
		}
228
		return $layerDefs;
229
	}
230
231
	/**
232
	 * Returns the HTML to display the map.
233
	 *
234
	 * @param array $params
235
	 * @param string $mapName
236
	 *
237
	 * @return string
238
	 */
239 20
	protected function getMapHTML( array $params, $mapName ) {
240 20
		return Html::rawElement(
241 20
			'div',
242
			[
243 20
				'id' => $mapName,
244 20
				'style' => "width: {$params['width']}; height: {$params['height']}; background-color: #cccccc; overflow: hidden;",
245 20
				'class' => 'maps-map maps-' . $this->service->getName()
246
			],
247 20
			wfMessage( 'maps-loading-map' )->inContentLanguage()->escaped() .
248 20
			Html::element(
249 20
				'div',
250 20
				[ 'style' => 'display:none', 'class' => 'mapdata' ],
251 20
				FormatJson::encode( $params )
252
			)
253
		);
254
	}
255
256 20
	public static function getLayerDependencies( $service, $params ) {
257 20
		global $egMapsOLLayerDependencies, $egMapsOLAvailableLayers,
258 20
			   $egMapsLeafletLayerDependencies, $egMapsLeafletAvailableLayers,
259 20
			   $egMapsLeafletLayersApiKeys;
260
261 20
		$layerDependencies = [];
262
263 20
		if ( $service === 'leaflet' ) {
264 13
			$layerNames = $params['layers'];
265 13
			foreach ( $layerNames as $layerName ) {
266 13
				if ( array_key_exists( $layerName, $egMapsLeafletAvailableLayers )
267 13
					&& $egMapsLeafletAvailableLayers[$layerName]
268 13
					&& array_key_exists( $layerName, $egMapsLeafletLayersApiKeys )
269 13
					&& array_key_exists( $layerName, $egMapsLeafletLayerDependencies ) ) {
270
					$layerDependencies[] = '<script src="' . $egMapsLeafletLayerDependencies[$layerName] .
271 13
						$egMapsLeafletLayersApiKeys[$layerName] . '"></script>';
272
				}
273
			}
274
		} else {
275 7
			if ( $service === 'openlayers' ) {
276
				$layerNames = $params['layers'];
277
				foreach ( $layerNames as $layerName ) {
278
					if ( array_key_exists( $layerName, $egMapsOLAvailableLayers ) // The layer must be defined in php
279
						&& is_array( $egMapsOLAvailableLayers[$layerName] ) // The layer must be an array...
280
						&& count( $egMapsOLAvailableLayers[$layerName] ) > 1 // ...with a second element...
281
						&& array_key_exists(
282
							$egMapsOLAvailableLayers[$layerName][1],
283
							$egMapsOLLayerDependencies
284
						) ) { //...that is a dependency.
285
						$layerDependencies[] = $egMapsOLLayerDependencies[$egMapsOLAvailableLayers[$layerName][1]];
286
					}
287
				}
288
289
			}
290
		}
291
292 20
		return array_unique( $layerDependencies );
293
	}
294
295
}
296