Completed
Push — address-as-title ( 9b6eeb...601934 )
by Peter
11:07
created

MapsLayerDefinition::addLayerToStore()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
nc 3
nop 1
dl 0
loc 21
rs 7.551
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Class for the 'layer' tag for describing a layer.
5
 * Most code of 'Maps_LayerPage.php' is reused for this since in 3.0 layer
6
 * pages can contain wikitext and a layer definition must be made through
7
 * this tag extension.
8
 *
9
 * @since 3.0
10
 *
11
 * @file Maps_Layer.php
12
 * @ingroup Maps
13
 *
14
 * @author Jeroen De Dauw
15
 * @author Daniel Werner
16
 */
17
class MapsLayerDefinition extends ParserHook {
0 ignored issues
show
Deprecated Code introduced by
The class ParserHook has been deprecated with message: since 1.0 in favour of the ParserHooks library

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
18
	
19
	/**
20
	 * The final layer representation of this tag.
21
	 *
22
	 * @since 3.0
23
	 *
24
	 * @var MapsLayer instance
25
	 */
26
	protected $layer;
27
	
28
	public function __construct() {
29
		/*
30
		 * make this a tag extension only to avoid weird parser function
31
		 * syntax as we have it in 'display_point' from the beginning!
32
		 */
33
		parent::__construct( true, false );
34
	}
35
	
36
	public static function initialize() {
37
		
38
	}
39
	
40
	/**
41
	 * Gets the name of the parser hook.
42
	 * @see ParserHook::getName
43
	 *
44
	 * @since 3.0
45
	 *
46
	 * @return string
47
	 */
48
	protected function getName() {
49
		return 'layer';
50
	}
51
	
52
	/**
53
	 * Returns an array containing the parameter info.
54
	 * @see ParserHook::getParameterInfo
55
	 *
56
	 * @since 3.0
57
	 *
58
	 * @return array
59
	 */
60
	protected function getParameterInfo( $type ) {
61
		$params = [];
62
63
		$params['type'] = [
64
			'default' => false,
65
			'manipulatedefault' => false,
66
			'message' => 'maps-displaymap-par-coordinates', // TODO-customMaps: create a message
67
		];
68
69
		$params['name'] = [
70
			'default' => false,
71
			'manipulatedefault' => false,
72
			// TODO-customMaps: addCriteria( new CriterionIsNonNumeric );
73
			'message' => 'maps-displaymap-par-coordinates', // TODO-customMaps: create a message
74
		];
75
76
		$params['definition'] = [
77
			'default' => false,
78
			'manipulatedefault' => false,
79
			'message' => 'maps-displaymap-par-coordinates', // TODO-customMaps: create a message
80
			'post-format' => function( $value ) {
81
				return MapsLayers::parseLayerParameters( $value, "\n", '=' );
82
			}
83
		];
84
85
		return $params;
86
	}
87
	
88
	/**
89
	 * Returns the list of default parameters.
90
	 * @see ParserHook::getDefaultParameters
91
	 *
92
	 * @since 3.0
93
	 *
94
	 * @return array
95
	 */
96
	protected function getDefaultParameters( $type ) {
97
		return [ 'definition' ];
98
	}
99
	
100
	/**
101
	 * Returns the parser function options.
102
	 * @see ParserHook::getFunctionOptions
103
	 *
104
	 * @since 3.0
105
	 *
106
	 * @return array
107
	 */
108
	protected function getFunctionOptions() {
109
		return [
110
			'noparse' => true,
111
			'isHTML' => true
112
		];
113
	}
114
115
	/**
116
	 * @see ParserHook::getMessage()
117
	 *
118
	 * @since 3.0
119
	 */
120
	public function getMessage() {
121
		return 'maps-layerdefinition-description';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 'maps-layerdefinition-description'; (string) is incompatible with the return type of the parent method ParserHook::getMessage of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
122
	}
123
	
124
	/**
125
	 * Returns the MapsLayerGroup with all layers of the same page which have been
126
	 * processed already. If the store is not attached to the parser object yet,
127
	 * an empty MapsLayerGroup will be attached as store after calling the function.
128
	 *
129
	 * @since 3.0
130
	 *
131
	 * @return MapsLayerGroup
132
	 */
133
	protected function getLayerStore() {
134
		$parserOutput = $this->parser->getOutput();
135
		
136
		// make sure layers store in current parsers ParserOutput is initialized:
137
		if( ! isset( $parserOutput->mExtMapsLayers ) ) {
138
			$parserOutput->mExtMapsLayers = new MapsLayerGroup();
139
		}
140
		return $parserOutput->mExtMapsLayers;
141
	}
142
	
143
	/**
144
	 * This will attach a user defined layer to the parser output of the parser which has
145
	 * started the <layer> rendering. All added layers will be stored in the database
146
	 * after page parsing.
147
	 * $layer will only be stored in case it is a subclass of MapsLayer and its definition
148
	 * is at least considered 'ok'.
149
	 *
150
	 * @since 3.0
151
	 *
152
	 * @param type $layer 
153
	 *
154
	 * @return boolean whether $layer has been added to the store
155
	 */
156
	protected function addLayerToStore( $layer ) {
157
		
158
		$store = $this->getLayerStore();
159
		
160
		// check whether $layer is a layer worthy to end up in the database:
161
		if( $layer === null || $layer === false
162
			|| ! is_subclass_of( $layer, 'MapsLayer' )
163
			|| ! $layer->isOk()
164
			|| $store->getLayerByName( $layer->getName() ) !== null // layer of same name in store already
165
			
166
		) {
167
			return false;
168
		}
169
		// add layer to store:
170
		$overwritten = $store->addLayer( $layer );
171
		
172
		if( $overwritten ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
173
			/** @ToDo: Message that a layer was defined twice on that site */
174
		}
175
		return true;
176
	}
177
	
178
	/**
179
	 * Renders and returns the output.
180
	 * @see ParserHook::renderTag
181
	 *
182
	 * @since 3.0
183
	 *
184
	 * @param array $parameters
185
	 *
186
	 * @return string
187
	 */
188
	public function render( array $parameters ) {
189
		global $wgLang;
190
191
		// Check whether parser tag is used in the right namespace context, abort if not
192
		if( $this->parser->getTitle()->getNamespace() !== Maps_NS_LAYER ) {
193
			global $wgContLang;
194
			return $this->rawErrorbox(
195
				wfMessage(
196
					'maps-layerdef-wrong-namespace',
197
					$wgContLang->getNsText( Maps_NS_LAYER )
198
				)->inContentLanguage()->text()
199
			);
200
		}
201
		
202
		$type = $parameters['type'];
203
		
204
		if( $type === false ) {
205
			// no layer type specified
206
			
207
			$availableLayerTypes = MapsLayerTypes::getAvailableTypes();
208
			
209
			$out = $this->rawErrorbox(
210
				wfMessage(
211
					'maps-error-no-layertype',
212
					$wgLang->listToText( $availableLayerTypes ),
213
					count( $availableLayerTypes )
214
				)->inContentLanguage()->text()
215
			);
216
		}
217
		elseif( MapsLayerTypes::hasType( $type ) ) {
218
			// get layer name if any:
219
			$name = $parameters['name'] !== false ? $parameters['name'] : null;
220
			
221
			// make sure the layer has a label, if no user data, make something up:
222
			if( empty( $parameters['definition']['label'] ) ) {
223
				if( $name !== null ) {
224
					$labelSuffix = "- $name";
225
				} else {
226
					// label for unnamed layer:
227
					$labelSuffix = '#' . ( count( $this->getLayerStore()->getLayers( MapsLayerGroup::LAYERS_NUMERIC ) ) + 1 );
228
				}
229
				$parameters['definition']['label'] = $this->parser->getTitle()->getText() . ' ' . $labelSuffix;
230
			}
231
232
			// new layer from user input (could still be invalid):
233
			$layer = MapsLayers::newLayerFromDefinition( $type, $parameters['definition'], $name );
234
			
235
			$out = $this->renderLayerInfo( $layer );
236
		}
237
		else {
238
			// specified layer type is non-existant!
239
			
240
			$availableLayerTypes = MapsLayerTypes::getAvailableTypes();
241
			
242
			$out = $this->rawErrorbox(
243
				wfMessage(
244
					'maps-error-invalid-layertype',
245
					$this->validator->getParameter('type')->getOriginalValue(),
0 ignored issues
show
Deprecated Code introduced by
The method ParamProcessor\Processor::getParameter() has been deprecated with message: since 1.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
246
					$wgLang->listToText( $availableLayerTypes ),
247
					count( $availableLayerTypes )
248
				)->inContentLanguage()->text()
249
			);
250
		}
251
252
		// add the layer to the store after all info has been rendered:
253
		$this->addLayerToStore( $layer );
0 ignored issues
show
Bug introduced by
The variable $layer does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Documentation introduced by
$layer is of type object<MapsLayer>, but the function expects a object<type>.

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...
254
255
		return $out;
256
	}
257
258
	/**
259
	 * Responsible for actual output on the layer page which gives an overview of the layer definition.
260
	 *
261
	 * @since 3.0
262
	 *
263
	 * @param MapsLayer
264
	 *
265
	 * @return string
266
	 */
267
	public function renderLayerInfo( MapsLayer $layer ) {
268
		global $wgLang;
269
270
		// appropriate layer header:
271
		if( $layer->getName() !== null ) {
272
273
			// if layer with same name is defined on same page already:
274
			if( $this->getLayerStore()->getLayerByName( $layer->getName() ) !== null ) {
275
				return
276
					$this->errorbox(
277
						wfMessage(
278
							'maps-layerdef-equal-layer-name',
279
							$layer->getName()
280
						)->inContentLanguage()->text()
281
					);
282
			}
283
			$outHeader = wfMessage(
284
				'maps-layer-of-type-and-name',
285
				$layer->getType(),
286
				$layer->getName()
287
			)->inContentLanguage()->text();
288
		}
289
		else {
290
			$outHeader = wfMessage(
291
				'maps-layer-of-type',
292
				$layer->getType()
293
			)->inContentLanguage()->text();
294
		}
295
		$outHeader = "<span class=\"mapslayerhead\">$outHeader</span>";
296
297
		// info message about which services are supporting the layer(-type):
298
		$supportedServices = MapsLayerTypes::getServicesForType( $layer->getType() );
299
		$outServices = '<span class="mapslayersupports">' .
300
			wfMessage(
301
				'maps-layer-type-supported-by',
302
				$wgLang->listToText( $supportedServices ),
303
				count( $supportedServices )
304
			)->inContentLanguage()->escaped() . '</span>';
305
306
		$outTable = $this->getLayerDefinitionTable( $layer );
307
308
		return
309
			Html::rawElement(
310
				'div',
311
				[ 'class' => 'mapslayer' . ( $layer->isOk() ? '' : ' mapslayererror' ) ],
312
				$outHeader . $outServices . $outTable
313
			);
314
	}
315
316
	/**
317
	 * Displays the layer definition as a table.
318
	 *
319
	 * @since 3.0
320
	 *
321
	 * @param MapsLayer $layer
322
	 *
323
	 * @return string
324
	 */
325
	protected function getLayerDefinitionTable( MapsLayer $layer ) {
326
		$out = '';
327
		$outWarning = '';
328
329
		// check whether any error occurred during parameter validaton:
330
		if ( ! $layer->isValid() ) {
331
			$messages = $layer->getErrorMessages();
332
			$warnings = '';
0 ignored issues
show
Unused Code introduced by
$warnings is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
333
			
334
			if( count( $messages ) === 1 ) {
335
				$warnings = htmlspecialchars( $messages[0] );
336
			} else {
337
				$warnings = '<ul><li>' . implode( '</li><li>', array_map( 'htmlspecialchars', $messages ) ) . '</li></ul>';
338
			}
339
			
340
			$warnings =
341
				'<tr><td class="mapslayerpropname">' .
342
				wfMessage(
343
					'maps-layerdef-invalid' . ( $layer->isOk() ? '' : '-fatal' ),
344
					count( $messages )
345
				)->inContentLanguage()->escaped() .
346
				"</td><td class=\"mapslayerpropval\">{$warnings}</td></tr>";
347
			
348
			$outWarning .= Html::rawElement(
349
					'table',
350
					[ 'width' => '100%', 'class' => ( $layer->isOk() ? 'mapslayerwarntable' : 'mapslayererrortable' ) ],
351
					$warnings
352
			);
353
354
			if( ! $layer->isOk() ) {
355
				// fatal error occurred, don't print definition table since this would be quite empty since
356
				// parameter validation aborted after fatal error parameter!
357
				return $outWarning;
358
			}
359
		}
360
		
361
		global $wgOut;
362
		$wgOut->addModules( 'ext.maps.layers' );
363
		
364
		$rows = [];
365
		
366
		// rows with layer definition:
367
		$properties = $layer->getPropertiesHtmlRepresentation( $this->parser );
368
				
369
		foreach ( $properties as $property => $value ) {
370
			$rows[] = Html::rawElement(
371
				'tr',
372
				[],
373
				Html::element(
374
					'td',
375
					[ 'class' => 'mapslayerpropname' ],
376
					$property
377
				) .
378
				Html::rawElement(
379
					'td',
380
					[ 'class' => 'mapslayerpropval' ],
381
					$value
382
				)
383
			);
384
		}
385
		
386
		$out .= Html::rawElement(
387
				'table',
388
				[ 'width' => '100%', 'class' => 'mapslayertable' ],
389
				implode( "\n", $rows )
390
		);
391
		
392
		return ( $out . $outWarning );
393
	}
394
	
395
	/**
396
	 * wraps text inside an error box.
397
	 *
398
	 * @since 3.0
399
	 *
400
	 * @param string  $text text of the error, html-escaped.
401
	 *
402
	 * @return string
403
	 */
404
	protected function errorbox( $text, $raw = true ) {
0 ignored issues
show
Unused Code introduced by
The parameter $raw 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...
405
		/**
406
		 * FIXME: using 'errorbox' isn't the best idea since it has
407
		 * some weird css definition, better would be introducing a
408
		 * own class and puttin the whole definition into a nicer box.
409
		 */
410
		return '<div class="errorbox" style="margin:0;">' . $text .'</div><div style="clear:both"></div>';
411
	}
412
	
413
	/**
414
	 * wraps text inside an error box.
415
	 *
416
	 * @since 3.0
417
	 *
418
	 * @param string $text text of the error, NOT html-escaped
419
	 *
420
	 * @return string
421
	 */
422
	protected function rawErrorbox( $text ) {
423
		$text = htmlspecialchars( $text );
424
		return $this->errorbox( $text );
425
	}
426
}
427