Completed
Push — master ( 484b1a...998a69 )
by Jeroen De
03:30
created

MapsDistanceParser::isDistance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * Static class for distance validation and parsing. Internal representatations are in meters.
5
 *
6
 * TODO: migrate to DataValue, ValueParser and ValueFormatter
7
 *
8
 * @since 0.6
9
 *
10
 * @licence GNU GPL v2+
11
 * @author Jeroen De Dauw < [email protected] >
12
 */
13
class MapsDistanceParser {
14
	
15
	private static $validatedDistanceUnit = false;
16
	
17
	private static $unitRegex = false;
18
	
19
	/**
20
	 * Parses a distance optionally containing a unit to a float value in meters.
21
	 * 
22
	 * @since 0.6
23
	 * 
24
	 * @param string $distance
25
	 * 
26
	 * @return float The distance in meters.
27
	 */
28 2
	public static function parseDistance( $distance ) {
29 2
		if ( !self::isDistance( $distance ) ) {
30 1
			return false;
31
		}
32
		
33 2
		$distance = self::normalizeDistance( $distance );
34
		
35 2
		self::initUnitRegex();
36
		
37 2
		$matches = [];
38 2
		preg_match( '/^\d+(\.\d+)?\s?(' . self::$unitRegex . ')?$/', $distance, $matches );
39
40 2
		$value = (float)( $matches[0] . $matches[1] );
41 2
		$value *= self::getUnitRatio( $matches[2] );
42
		
43 2
		return $value;
44
	}
45
	
46
	/**
47
	 * Formats a given distance in meters to a distance in an optionally specified notation.
48
	 * 
49
	 * @since 0.6
50
	 * 
51
	 * @param float $meters
52
	 * @param string $unit
53
	 * @param integer $decimals
54
	 * 
55
	 * @return string
56
	 */
57 2
	public static function formatDistance( $meters, $unit = null, $decimals = 2 ) {
58 2
		global $wgContLang;
59 2
		$meters = $wgContLang->formatNum( round( $meters / self::getUnitRatio( $unit ), $decimals ) );
60 2
		return "$meters $unit";
61
	}
62
	
63
	/**
64
	 * Shortcut for converting from one unit to another.
65
	 * 
66
	 * @since 0.6
67
	 * 
68
	 * @param string $distance
69
	 * @param string $unit
70
	 * @param integer $decimals
71
	 * 
72
	 * @return string
73
	 */
74 1
	public static function parseAndFormat( $distance, $unit = null, $decimals = 2 ) {
75 1
		return self::formatDistance( self::parseDistance( $distance ), $unit, $decimals );
0 ignored issues
show
Security Bug introduced by
It seems like self::parseDistance($distance) targeting MapsDistanceParser::parseDistance() can also be of type false; however, MapsDistanceParser::formatDistance() does only seem to accept double, did you maybe forget to handle an error condition?
Loading history...
76
	}
77
	
78
	/**
79
	 * Returns if the provided string is a valid distance.
80
	 * 
81
	 * @since 0.6
82
	 * 
83
	 * @param string $distance
84
	 * 
85
	 * @return boolean
86
	 */
87 3
	public static function isDistance( $distance ) {
88 3
		$distance = self::normalizeDistance( $distance );
89
		
90 3
		self::initUnitRegex();
91
92 3
		return (bool)preg_match( '/^\d+(\.\d+)?\s?(' . self::$unitRegex . ')?$/', $distance );
93
	}
94
	
95
	/**
96
	 * Returns the unit to meter ratio in a safe way, by first resolving the unit.
97
	 * 
98
	 * @since 0.6.2
99
	 * 
100
	 * @param string $unit
101
	 * 
102
	 * @return float
103
	 */
104 4
	public static function getUnitRatio( $unit = null ) {
105 4
		global $egMapsDistanceUnits;
106 4
		return $egMapsDistanceUnits[self::getValidUnit( $unit )];
107
	}
108
	
109
	/**
110
	 * Returns a valid unit. If the provided one is invalid, the default will be used.
111
	 * 
112
	 * @since 0.6.2
113
	 * 
114
	 * @param string $unit
115
	 * 
116
	 * @return string
117
	 */
118 5
	public static function getValidUnit( $unit = null ) {
119 5
		global $egMapsDistanceUnit, $egMapsDistanceUnits;
120
		
121
		// This ensures the value for $egMapsDistanceUnit is correct, and caches the result.
122 5
		if ( self::$validatedDistanceUnit === false ) {
123 1
			if ( !array_key_exists( $egMapsDistanceUnit, $egMapsDistanceUnits ) ) {
124
				$units = array_keys( $egMapsDistanceUnits );
125
				$egMapsDistanceUnit = $units[0];
126
			}
127
			
128 1
			self::$validatedDistanceUnit = true;
129 1
		}		
130
		
131 5
		if ( $unit == null || !array_key_exists( $unit, $egMapsDistanceUnits ) ) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $unit of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
132 2
			$unit = $egMapsDistanceUnit;
133 2
		}
134
135 5
		return $unit;
136
	}
137
	
138
	/**
139
	 * Returns a list of all suported units.
140
	 * 
141
	 * @since 0.6
142
	 * 
143
	 * @return array
144
	 */
145 1
	public static function getUnits() {
146 1
		global $egMapsDistanceUnits;
147 1
		return array_keys( $egMapsDistanceUnits );
148
	}
149
	
150
	/**
151
	 * Normalizes a potential distance by removing spaces and truning comma's into dots.
152
	 * 
153
	 * @since 0.6.5
154
	 * 
155
	 * @param $distance String
156
	 * 
157
	 * @return string
158
	 */
159 3
	protected static function normalizeDistance( $distance ) {
160 3
		$distance = trim( (string)$distance );
161 3
		$strlen = strlen( $distance );
162
		
163 3
		for ( $i = 0; $i < $strlen; $i++ ) {
164 3
			if ( !ctype_digit( $distance{$i} ) && !in_array( $distance{$i}, [ ',', '.' ] ) ) {
165 3
				$value = substr( $distance, 0, $i );
166 3
				$unit = substr( $distance, $i );
167 3
				break;
168
			}
169 3
		}
170
		
171 3
		$value = str_replace( ',', '.', isset( $value ) ? $value : $distance );
172
		
173 3
		if ( isset( $unit ) ) {
174 3
			$value .= ' ' . str_replace( [ ' ', "\t" ], '', $unit );
175 3
		}
176
177 3
		return $value;
178
	}
179
	
180 3
	private static function initUnitRegex() {
181 3
		if ( self::$unitRegex === false ) {
182 1
			global $egMapsDistanceUnits;
183 1
			self::$unitRegex = implode( '|', array_keys( $egMapsDistanceUnits ) ) . '|';
0 ignored issues
show
Documentation Bug introduced by
The property $unitRegex was declared of type boolean, but implode('|', array_keys(...psDistanceUnits)) . '|' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
184 1
		}		
185 3
	}
186
	
187
}