Completed
Push — master ( cfeaf0...56caea )
by Jeroen De
11:10 queued 09:51
created

MapsDistanceParser::parseAndFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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