Passed
Push — main ( 683f0d...4c4127 )
by Will
04:44 queued 53s
created

versions   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Test Coverage

Coverage 84.93%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 76
c 1
b 0
f 0
dl 0
loc 165
ccs 62
cts 73
cp 0.8493
rs 9.44
wmc 37

4 Methods

Rating   Name   Duplication   Size   Complexity  
B released() 0 25 7
B load() 0 32 10
A latest() 0 14 4
C get() 0 68 16
1
<?php
2
declare(strict_types = 1);
3
namespace hexydec\agentzero;
4
5
class versions {
6
7
	/**
8
	 * @var array|false|null An array of browser version numbers
9
	 */
10
	protected static array|false|null $versions = null;
11
12
	/**
13
	 * Loads browser version information from an external source
14
	 * 
15
	 * @param string $source The URL of the source JSON containing the version information
16
	 * @param string $cache The absolute file address of the cache file
17
	 * @param ?int $life The maximum life of the cache file in seconds
18
	 * @return array<string,array<string,string>> An array of browser versioning information, or false if the data source not be retrieved
19
	 */
20 96
	protected static function load(string $source, string $cache, ?int $life = 604800) : array|false {
21
22
		// cache for this session
23 96
		$data = self::$versions;
24 96
		if ($data === null) {
25
26
			// fetch from cache
27 1
			if (\file_exists($cache) && \filemtime($cache) > \time() - $life && ($json = \file_get_contents($cache)) !== false) {
28
29
			// fetch from server
30 1
			} elseif (($json = \file_get_contents($source)) === false) {
31
32
				// get stale cache
33
				if ($cache !== null && ($json = \file_get_contents($cache)) !== false) {
34
					self::$versions = false;
35
					return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array<string,array<string,string>>.
Loading history...
36
				}
37
38
			// update cache
39
			} else {
40
41
				// create directory and cache file
42 1
				$dir = \dirname($cache);
43 1
				if (\is_dir($dir) || \mkdir($dir, 0755)) {
44 1
					\file_put_contents($cache, $json);
45
				}
46
			}
47
48
			// decode JSON
49 1
			self::$versions = $data = \json_decode($json, true);
50
		}
51 96
		return $data ?? false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data ?? false could also return false which is incompatible with the documented return type array<string,array<string,string>>. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
52
	}
53
54
	/**
55
	 * Determines the latest version of a browser, optionally capped by the supplied date
56
	 * 
57
	 * @param array<string,string> $versions An array of browser versions, where the key is the version number and the value is the release date (In Ymd format)
58
	 */
59 91
	protected static function latest(array $versions, ?\DateTime $now = null) : ?string {
60
61
		// no date restriction
62 91
		if ($now === null) {
63 91
			return \strval(\array_key_first($versions));
64
		} else {
65
			$date = \intval($now->format('Ymd'));
66
			foreach ($versions AS $key => $item) {
67
				if ($date < $item) {
68
					return \strval($key);
69
				}
70
			}
71
		}
72
		return null;
73
	}
74
75 90
	protected static function released(array $data, string $version) : ?string {
76 90
		$major = \intval($version);
77 90
		$len = 0;
78 90
		$i = 0;
79 90
		$vlen = \strlen($version);
80 90
		$released = null;
81 90
		foreach ($data AS $ver => $date) {
82 90
			if (\intval($ver) === $major) {
83 90
				$ver = \strval($ver); // cast as string to get letters, string keys cast to int when array keys
84 90
				$match = 0;
85 90
				for ($n = 0; $n < $vlen; $n++) {
86 90
					if ($version[$n] === ($ver[$n] ?? null)) {
87 90
						$match++;
88
					} else {
89 83
						break;
90
					}
91
				}
92 90
				if ($match > $len) {
93 90
					$len = $match;
94 90
					$released = $date;
95
				}
96
			}
97 90
			$i++;
98
		}
99 90
		return $released !== null ? (new \DateTime(\strval($released)))->format('Y-m-d') : null;
0 ignored issues
show
introduced by
The condition $released !== null is always false.
Loading history...
100
	}
101
102 96
	public static function get(string $browser, string $version, array $config) : array {
103 96
		$source = $config['versionssource'];
104 96
		$cache = $config['versionscache'];
105 96
		$life = $config['versionscachelife'];
106 96
		if ($cache !== null && ($versions = self::load($source, $cache, $life)) !== false) {
107 96
			$data = [];
108
109
			// get latest version of the browser
110 96
			if (isset($versions[$browser]) && ($data['browserlatest'] = self::latest($versions[$browser], $config['currentdate'])) !== null) {
111
				
112
				// check if version is greater than latest version
113 91
				$major = \intval($version);
114 91
				$latest = \intval($data['browserlatest']);
115 91
				$first = \intval(\array_key_last($versions[$browser]));
116
117
				// version is way out of bounds (This happens sometimes, for example if the safari engine version is reported instead of the browser version)
118 91
				if ($latest + 3 < $major) {
119 27
					return [];
120
121
				// nightly build?
122 91
				} elseif ($latest + 3 === $major) {
123 2
					$data['browserstatus'] = 'nightly';
124
125
				// canary build
126 91
				} elseif ($latest + 2 === $major) {
127
					$data['browserstatus'] = 'canary';
128
129
				// beta release
130 91
				} elseif ($latest + 1 === $major) {
131
					$data['browserstatus'] = 'beta';
132
133
				// so old we don't have data for it
134 91
				} elseif ($major < $first) {
135 22
					$data['browserstatus'] = 'legacy';
136
137
				// find closes match for version
138
				} else {
139
140
					// get current version
141 90
					$data['browserreleased'] = self::released($versions[$browser], $version);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $data['browserreleased'] is correct as self::released($versions[$browser], $version) targeting hexydec\agentzero\versions::released() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
142
143
					// calculate status
144 90
					if (isset($data['browserreleased'], $data['browserlatest'])) {
145 90
						$current = \explode('.', $data['browserlatest'])[0] === \explode('.', $version)[0];
146 90
						$released = new \DateTime($data['browserreleased']);
147
148
						// legacy
149 90
						if ($released < \date_create('-5 years')) {
150 74
							$data['browserstatus'] = 'legacy';
151
152
						// outdated
153 63
						} elseif ($released < \date_create('-2 years')) {
154 59
							$data['browserstatus'] = 'outdated';
155
156
						// current
157 13
						} elseif ($current && ($released >= \date_create('-1 year') || $data['browserlatest'] === $version)) {
158 1
							$data['browserstatus'] = 'current';
159
160
						// previous
161
						} else {
162 12
							$data['browserstatus'] = 'previous';
163
						}
164
					}
165
				}
166
			}
167 96
			return $data;
168
		}
169
		return [];
170
	}
171
}