Completed
Push — move ( 889447...795827 )
by Jeroen De
05:20
created

MapsDistanceParser::getUnitRatio()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Maps\Presentation;
4
5
/**
6
 * Static class for distance validation and parsing. Internal representations are in meters.
7
 *
8
 * @licence GNU GPL v2+
9
 * @author Jeroen De Dauw < [email protected] >
10
 */
11
class MapsDistanceParser {
12
13
	private static $validatedDistanceUnit = false;
14
15
	private static $unitRegex = false;
16
17 1
	public static function parseAndFormat( string $distance, string $unit = null, int $decimals = 2 ): string {
18 1
		return self::formatDistance( self::parseDistance( $distance ), $unit, $decimals );
0 ignored issues
show
Security Bug introduced by
It seems like self::parseDistance($distance) targeting Maps\Presentation\MapsDi...Parser::parseDistance() can also be of type false; however, Maps\Presentation\MapsDi...arser::formatDistance() does only seem to accept double, did you maybe forget to handle an error condition?
Loading history...
19
	}
20
21
	/**
22
	 * Formats a given distance in meters to a distance in an optionally specified notation.
23
	 */
24 10
	public static function formatDistance( float $meters, string $unit = null, int $decimals = 2 ): string {
25 10
		global $wgContLang;
26 10
		$meters = $wgContLang->formatNum( round( $meters / self::getUnitRatio( $unit ), $decimals ) );
27 10
		return "$meters $unit";
28
	}
29
30
	/**
31
	 * Returns the unit to meter ratio in a safe way, by first resolving the unit.
32
	 */
33 71
	public static function getUnitRatio( string $unit = null ): float {
34 71
		global $egMapsDistanceUnits;
35 71
		return $egMapsDistanceUnits[self::getValidUnit( $unit )];
36
	}
37
38
	/**
39
	 * Returns a valid unit. If the provided one is invalid, the default will be used.
40
	 */
41 72
	public static function getValidUnit( string $unit = null ): string {
42 72
		global $egMapsDistanceUnit, $egMapsDistanceUnits;
43
44
		// This ensures the value for $egMapsDistanceUnit is correct, and caches the result.
45 72
		if ( self::$validatedDistanceUnit === false ) {
46 1
			if ( !array_key_exists( $egMapsDistanceUnit, $egMapsDistanceUnits ) ) {
47
				$units = array_keys( $egMapsDistanceUnits );
48
				$egMapsDistanceUnit = $units[0];
49
			}
50
51 1
			self::$validatedDistanceUnit = true;
52
		}
53
54 72
		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 null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
55 24
			$unit = $egMapsDistanceUnit;
56
		}
57
58 72
		return $unit;
59
	}
60
61
	/**
62
	 * Parses a distance optionally containing a unit to a float value in meters.
63
	 *
64
	 * @param string $distance
65
	 *
66
	 * @return float|false The distance in meters or false on failure
67
	 */
68 67
	public static function parseDistance( string $distance ) {
69 67
		if ( !self::isDistance( $distance ) ) {
70 1
			return false;
71
		}
72
73 67
		$distance = self::normalizeDistance( $distance );
74
75 67
		self::initUnitRegex();
76
77 67
		$matches = [];
78 67
		preg_match( '/^\d+(\.\d+)?\s?(' . self::$unitRegex . ')?$/', $distance, $matches );
79
80 67
		$value = (float)( $matches[0] . $matches[1] );
81 67
		$value *= self::getUnitRatio( $matches[2] );
82
83 67
		return $value;
84
	}
85
86 68
	public static function isDistance( string $distance ): bool {
87 68
		$distance = self::normalizeDistance( $distance );
88
89 68
		self::initUnitRegex();
90
91 68
		return (bool)preg_match( '/^\d+(\.\d+)?\s?(' . self::$unitRegex . ')?$/', $distance );
92
	}
93
94
	/**
95
	 * Normalizes a potential distance by removing spaces and truning comma's into dots.
96
	 */
97 68
	protected static function normalizeDistance( string $distance ): string {
98 68
		$distance = trim( (string)$distance );
99 68
		$strlen = strlen( $distance );
100
101 68
		for ( $i = 0; $i < $strlen; $i++ ) {
102 68
			if ( !ctype_digit( $distance{$i} ) && !in_array( $distance{$i}, [ ',', '.' ] ) ) {
103 46
				$value = substr( $distance, 0, $i );
104 46
				$unit = substr( $distance, $i );
105 46
				break;
106
			}
107
		}
108
109 68
		$value = str_replace( ',', '.', isset( $value ) ? $value : $distance );
110
111 68
		if ( isset( $unit ) ) {
112 46
			$value .= ' ' . str_replace( [ ' ', "\t" ], '', $unit );
113
		}
114
115 68
		return $value;
116
	}
117
118 68
	private static function initUnitRegex() {
119 68
		if ( self::$unitRegex === false ) {
120 1
			global $egMapsDistanceUnits;
121 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...
122
		}
123 68
	}
124
125
	/**
126
	 * Returns a list of all suported units.
127
	 */
128 19
	public static function getUnits(): array {
129 19
		global $egMapsDistanceUnits;
130 19
		return array_keys( $egMapsDistanceUnits );
131
	}
132
133
}
134