versions::released()   B
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 25
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 19
c 0
b 0
f 0
nc 12
nop 2
dl 0
loc 25
ccs 19
cts 19
cp 1
crap 7
rs 8.8333
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 96
			if (isset($versions[$browser])) {
109
110
				// get latest version of the browser
111 91
				$data['browserlatest'] = self::latest($versions[$browser], $config['currentdate']);
112
				
113
				// check if version is greater than latest version
114 91
				$major = \intval($version);
115 91
				$latest = \intval($data['browserlatest']);
116 91
				$first = \intval(\array_key_last($versions[$browser]));
117
118
				// version is way out of bounds (This happens sometimes, for example if the safari engine version is reported instead of the browser version)
119 91
				if ($latest + 3 < $major) {
120 24
					return [];
121
122
				// nightly build?
123 91
				} elseif ($latest + 3 === $major) {
124 1
					$data['browserstatus'] = 'nightly';
125
126
				// canary build
127 91
				} elseif ($latest + 2 === $major) {
128
					$data['browserstatus'] = 'canary';
129
130
				// beta release
131 91
				} elseif ($latest + 1 === $major) {
132
					$data['browserstatus'] = 'beta';
133
134
				// so old we don't have data for it
135 91
				} elseif ($major < $first) {
136 22
					$data['browserstatus'] = 'legacy';
137
138
				// find closes match for version
139
				} else {
140
141
					// get current version
142 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...
143
144
					// calculate status
145 90
					if (isset($data['browserreleased'])) {
146 90
						$current = \explode('.', $data['browserlatest'])[0] === \explode('.', $version)[0];
0 ignored issues
show
Bug introduced by
It seems like $data['browserlatest'] can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

146
						$current = \explode('.', /** @scrutinizer ignore-type */ $data['browserlatest'])[0] === \explode('.', $version)[0];
Loading history...
147 90
						$released = new \DateTime($data['browserreleased']);
148
149
						// legacy
150 90
						if ($released < \date_create('-5 years')) {
151 75
							$data['browserstatus'] = 'legacy';
152
153
						// outdated
154 64
						} elseif ($released < \date_create('-2 years')) {
155 56
							$data['browserstatus'] = 'outdated';
156
157
						// current
158 25
						} elseif ($current && ($released >= \date_create('-1 year') || $data['browserlatest'] === $version)) {
159 1
							$data['browserstatus'] = 'current';
160
161
						// previous
162
						} else {
163 24
							$data['browserstatus'] = 'previous';
164
						}
165
					}
166
				}
167
			}
168 96
			return $data;
169
		}
170
		return [];
171
	}
172
}