Completed
Push — leaflet-layers ( df0997...51607c )
by Peter
08:52
created

MapsDisplayMapRenderer::getLayerDependencies()   C

Complexity

Conditions 9
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
eloc 14
nc 3
nop 2
dl 0
loc 22
ccs 0
cts 12
cp 0
crap 90
rs 6.412
c 0
b 0
f 0
1
<?php
2
use Maps\Element;
3
use Maps\Elements\Line;
4
use Maps\Elements\Location;
5
6
/**
7
 * Class handling the #display_map rendering.
8
 *
9
 * @licence GNU GPL v2+
10
 * @author Jeroen De Dauw < [email protected] >
11
 * @author Kim Eik
12
 */
13
class MapsDisplayMapRenderer {
14
15
	/**
16
	 * @since 2.0
17
	 *
18
	 * @var iMappingService
19
	 */
20
	protected $service;
21
22
	/**
23
	 * Constructor.
24
	 *
25
	 * @param iMappingService $service
26
	 */
27
	public function __construct( iMappingService $service ) {
28
		$this->service = $service;
29
	}
30
31
	/**
32
	 * Returns the HTML to display the map.
33
	 *
34
	 * @since 2.0
35
	 *
36
	 * @param array $params
37
	 * @param Parser $parser
38
	 * @param string $mapName
39
	 *
40
	 * @return string
41
	 */
42
	protected function getMapHTML( array $params, Parser $parser, $mapName ) {
43
		return Html::rawElement(
44
			'div',
45
			[
46
				'id' => $mapName,
47
				'style' => "width: {$params['width']}; height: {$params['height']}; background-color: #cccccc; overflow: hidden;",
48
				'class' => 'maps-map maps-' . $this->service->getName()
49
			],
50
			wfMessage( 'maps-loading-map' )->inContentLanguage()->escaped() .
51
				Html::element(
52
					'div',
53
					[ 'style' => 'display:none', 'class' => 'mapdata' ],
54
					FormatJson::encode( $this->getJSONObject( $params, $parser ) )
55
				)
56
		);
57
	}
58
59
	/**
60
	 * Returns a PHP object to encode to JSON with the map data.
61
	 *
62
	 * @since 2.0
63
	 *
64
	 * @param array $params
65
	 * @param Parser $parser
66
	 *
67
	 * @return mixed
68
	 */
69
	protected function getJSONObject( array $params, Parser $parser ) {
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
70
		return $params;
71
	}
72
73
	/**
74
	 * Handles the request from the parser hook by doing the work that's common for all
75
	 * mapping services, calling the specific methods and finally returning the resulting output.
76
	 *
77
	 * @param array $params
78
	 * @param Parser $parser
79
	 *
80
	 * @return string
81
	 */
82
	public final function renderMap( array $params, Parser $parser ) {
83
		$this->handleMarkerData( $params, $parser );
84
85
		$mapName = $this->service->getMapId();
86
87
		$output = $this->getMapHTML( $params, $parser, $mapName );
88
89
		$configVars = Skin::makeVariablesScript( $this->service->getConfigVariables() );
90
91
		if ( isset( $params['layer'] ) ) {
92
			$params['layers'] = [$params['layer']];
93
		}
94
		$layerDependencies = self::getLayerDependencies( $params['mappingservice'], $params['layers'] );
0 ignored issues
show
Bug introduced by
It seems like $params['layers'] can also be of type null; however, MapsDisplayMapRenderer::getLayerDependencies() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
95
		$this->service->addLayerDependencies( array_unique( $layerDependencies ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface iMappingService as the method addLayerDependencies() does only exist in the following implementations of said interface: MapsLeaflet, MapsOpenLayers.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
96
97
		$this->service->addDependencies( $parser );
98
		$parser->getOutput()->addHeadItem( $configVars );
99
100
		return $output;
101
	}
102
103
	/**
104
	 * Converts the data in the coordinates parameter to JSON-ready objects.
105
	 * These get stored in the locations parameter, and the coordinates on gets deleted.
106
	 *
107
	 * FIXME: complexity
108
	 *
109
	 * @since 1.0
110
	 *
111
	 * @param array &$params
112
	 * @param Parser $parser
113
	 */
114
	protected function handleMarkerData( array &$params, Parser $parser ) {
115
		if ( is_object( $params['centre'] ) ) {
116
			$params['centre'] = $params['centre']->getJSONObject();
117
		}
118
119
		$parserClone = clone $parser;
120
121
		if ( is_object( $params['wmsoverlay'] ) ) {
122
			$params['wmsoverlay'] = $params['wmsoverlay']->getJSONObject();
123
		}
124
125
		$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...
126
		$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...
127
		$params['locations'] = [];
128
129
		/**
130
		 * @var Location $location
131
		 */
132
		foreach ( $params['coordinates'] as $location ) {
133
			$jsonObj = $location->getJSONObject( $params['title'], $params['label'], $iconUrl, '', '',$visitedIconUrl);
134
135
			$jsonObj['title'] = $parserClone->parse( $jsonObj['title'], $parserClone->getTitle(), new ParserOptions() )->getText();
136
			$jsonObj['text'] = $parserClone->parse( $jsonObj['text'], $parserClone->getTitle(), new ParserOptions() )->getText();
137
			if ( isset( $jsonObj['inlineLabel'] ) ) {
138
				$jsonObj['inlineLabel'] = strip_tags($parserClone->parse( $jsonObj['inlineLabel'], $parserClone->getTitle(), new ParserOptions() )->getText(),'<a><img>');
139
			}
140
141
			$hasTitleAndtext = $jsonObj['title'] !== '' && $jsonObj['text'] !== '';
142
			$jsonObj['text'] = ( $hasTitleAndtext ? '<b>' . $jsonObj['title'] . '</b><hr />' : $jsonObj['title'] ) . $jsonObj['text'];
143
			$jsonObj['title'] = strip_tags( $jsonObj['title'] );
144
145
			$params['locations'][] = $jsonObj;
146
		}
147
148
		unset( $params['coordinates'] );
149
150
		$this->handleShapeData( $params, $parserClone );
151
152
		if ( $params['mappingservice'] === 'openlayers' ) {
153
			$params['layers'] = self::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...
154
		}
155
	}
156
157
	protected function handleShapeData( array &$params, Parser $parserClone ) {
158
		$textContainers = [
159
			&$params['lines'] ,
160
			&$params['polygons'] ,
161
			&$params['circles'] ,
162
			&$params['rectangles'],
163
			&$params['imageoverlays'], // FIXME: this is Google Maps specific!!
164
		];
165
166
		foreach ( $textContainers as &$textContainer ) {
167
			if ( is_array( $textContainer ) ) {
168
				foreach ( $textContainer as &$obj ) {
169
					if ( $obj instanceof Element ) {
170
						$obj = $obj->getArrayValue();
171
					}
172
173
					$obj['title'] = $parserClone->parse( $obj['title'] , $parserClone->getTitle() , new ParserOptions() )->getText();
174
					$obj['text'] = $parserClone->parse( $obj['text'] , $parserClone->getTitle() , new ParserOptions() )->getText();
175
176
					$hasTitleAndtext = $obj['title'] !== '' && $obj['text'] !== '';
177
					$obj['text'] = ( $hasTitleAndtext ? '<b>' . $obj['title'] . '</b><hr />' : $obj['title'] ) . $obj['text'];
178
					$obj['title'] = strip_tags( $obj['title'] );
179
				}
180
			}
181
		}
182
	}
183
184
	/**
185
	 * FIXME
186
	 *
187
	 * Temporary hack until the mapping service handling gets a proper refactor
188
	 * This kind of JS construction is also rather evil and should not be done at this point
189
	 *
190
	 * @since 3.0
191
	 * @deprecated
192
	 *
193
	 * @param string[] $layers
194
	 *
195
	 * @return string[]
196
	 */
197
	public static function evilOpenLayersHack( $layers ) {
198
		global $egMapsOLLayerGroups, $egMapsOLAvailableLayers;
199
200
		$layerDefs = [];
201
		$layerNames = [];
202
203
		foreach ( $layers as $layerOrGroup ) {
204
			$lcLayerOrGroup = strtolower( $layerOrGroup );
205
206
			// Layer groups. Loop over all items and add them if not present yet:
207
			if ( array_key_exists( $lcLayerOrGroup, $egMapsOLLayerGroups ) ) {
208
				foreach ( $egMapsOLLayerGroups[$lcLayerOrGroup] as $layerName ) {
209
					if ( !in_array( $layerName, $layerNames ) ) {
210
						if ( is_array( $egMapsOLAvailableLayers[$layerName] ) ) {
211
							$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$layerName][0];
212
						}
213
						else {
214
							$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$layerName];
215
						}
216
						$layerNames[] = $layerName;
217
					}
218
				}
219
			}
220
			// Single layers. Add them if not present yet:
221
			elseif ( array_key_exists( $lcLayerOrGroup, $egMapsOLAvailableLayers ) ) {
222
				if ( !in_array( $lcLayerOrGroup, $layerNames ) ) {
223
					if ( is_array( $egMapsOLAvailableLayers[$lcLayerOrGroup] ) ) {
224
						$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$lcLayerOrGroup][0];
225
					}
226
					else {
227
						$layerDefs[] = 'new ' . $egMapsOLAvailableLayers[$lcLayerOrGroup];
228
					}
229
230
					$layerNames[] = $lcLayerOrGroup;
231
				}
232
			}
233
		}
234
		return $layerDefs;
235
	}
236
237
	public static function getLayerDependencies( $service, array $layerNames ) {
0 ignored issues
show
Coding Style introduced by
getLayerDependencies 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...
238
		global $egMapsOLLayerDependencies, $egMapsOLAvailableLayers;
239
240
		$layerDependencies = [];
241
242
		if ( $service === 'leaflet' && in_array( 'MapQuestOpen', $layerNames ) ) {
243
			$layerDependencies[] = "<script src='https://open.mapquestapi.com/sdk/leaflet/v2.2/mq-map.js?key=" .
244
					$GLOBALS['egMapsLeafletLayersApiKeys']['MapQuestOpen'] . "'></script>";
245
		} else if ( $service === 'openlayers' ) {
246
			foreach ( $layerNames as $layerName ) {
247
				if ( array_key_exists( $layerName, $egMapsOLAvailableLayers ) // The layer must be defined in php
248
						&& is_array( $egMapsOLAvailableLayers[$layerName] ) // The layer must be an array...
249
						&& count( $egMapsOLAvailableLayers[$layerName] ) > 1 // ...with a second element...
250
						&& array_key_exists( $egMapsOLAvailableLayers[$layerName][1], $egMapsOLLayerDependencies ) ) { //...that is a dependency.
251
					$layerDependencies[] = $egMapsOLLayerDependencies[$egMapsOLAvailableLayers[$layerName][1]];
252
				}
253
			}
254
255
		}
256
257
		return array_unique( $layerDependencies );
258
	}
259
260
}
261