From a4128edd2a8d4cda440de6b2802412924536ffa6 Mon Sep 17 00:00:00 2001 From: Hexydec Date: Mon, 27 Apr 2026 16:50:22 +0100 Subject: [PATCH 1/3] Fixed issue in `agentzero::__construct()` where the returned type for version values could be an int, whereas they are stored as strings, they are now converted. Fixed bug in `versions::get()` where if the specified version was out of range of the earliest version we have data for, and the major version was the same, it would incorrect return that it was the latest version. It now checks the string against the earliest version to see if it lower, and if so marks it as `legacy`. Updated tests. --- src/agentzero.php | 12 ++++++------ src/helpers/versions.php | 32 ++++++++++++++++++++++++++------ tests/browsersTest.php | 10 ++++------ tests/clientHintsTest.php | 2 +- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/agentzero.php b/src/agentzero.php index 9b04fc0..320f5a5 100644 --- a/src/agentzero.php +++ b/src/agentzero.php @@ -89,13 +89,13 @@ private function __construct(string $ua, \stdClass $data) { // platform $this->kernel = $data->kernel ?? null; $this->platform = $data->platform ?? null; - $this->platformversion = $data->platformversion ?? null; + $this->platformversion = !empty($data->platformversion) ? \strval($data->platformversion) : null; // browser $this->engine = $data->engine ?? null; - $this->engineversion = $data->engineversion ?? null; + $this->engineversion = !empty($data->engineversion) ? \strval($data->engineversion) : null; $this->browser = $data->browser ?? null; - $this->browserversion = $data->browserversion ?? null; + $this->browserversion = !empty($data->browserversion) ? \strval($data->browserversion) : null; $this->browserstatus = $data->browserstatus ?? null; $this->browserreleased = !empty($data->browserreleased) ? $data->browserreleased : null; $this->browserlatest = $data->browserlatest ?? null; @@ -104,9 +104,9 @@ private function __construct(string $ua, \stdClass $data) { // app $this->app = $data->app ?? null; $this->appname = $data->appname ?? null; - $this->appversion = $data->appversion ?? null; + $this->appversion = !empty($data->appversion) ? \strval($data->appversion) : null; $this->framework = $data->framework ?? null; - $this->frameworkversion = $data->frameworkversion ?? null; + $this->frameworkversion = !empty($data->frameworkversion) ? \strval($data->frameworkversion) : null; $this->url = $data->url ?? null; // network @@ -251,7 +251,7 @@ public static function parse(string $ua, array $hints = [], array $config = []) // lowercase the tokens $tokenslower = []; foreach ($tokens AS $key => $item) { - $tokenslower[$key] = $item; + $tokenslower[$key] = \mb_strtolower($item); } // extract UA info diff --git a/src/helpers/versions.php b/src/helpers/versions.php index 8ec4b62..f9c1bad 100644 --- a/src/helpers/versions.php +++ b/src/helpers/versions.php @@ -97,6 +97,27 @@ protected static function released(array $data, string $version) : ?string { return !empty($released) ? (new \DateTime(\strval($released)))->format('Y-m-d') : null; } + protected static function legacy(array $browsers, string $version) : bool { + $earliest = \strval(\array_key_last($browsers)); + + // check if we have a version in range + $parts = \explode('.', $version); + foreach (\explode('.', $earliest) AS $key => $item) { + if (isset($parts[$key])) { + + // older than earlier version we have a date for + if ($item > $parts[$key]) { + return true; + + // newer than the earlier version we have a date for + } elseif ($item < $parts[$key]) { + return false; + } + } + } + return false; + } + public static function get(string $browser, string $version, array $config) : array { $source = $config['versionssource']; $cache = $config['versionscache']; @@ -110,10 +131,13 @@ public static function get(string $browser, string $version, array $config) : ar // check if version is greater than latest version $major = \intval($version); $latest = \intval($data['browserlatest']); - $first = \intval(\array_key_last($versions[$browser])); + + // check if we have a version in range + if (self::legacy($versions[$browser], $version)) { + $data['browserstatus'] = 'legacy'; // version is way out of bounds (This happens sometimes, for example if the safari engine version is reported instead of the browser version) - if ($latest + 3 < $major) { + } elseif ($latest + 3 < $major) { return []; // nightly build? @@ -128,10 +152,6 @@ public static function get(string $browser, string $version, array $config) : ar } elseif ($latest + 1 === $major) { $data['browserstatus'] = 'beta'; - // so old we don't have data for it - } elseif ($major < $first) { - $data['browserstatus'] = 'legacy'; - // find closes match for version } else { diff --git a/tests/browsersTest.php b/tests/browsersTest.php index 3090ca6..14ed003 100644 --- a/tests/browsersTest.php +++ b/tests/browsersTest.php @@ -499,8 +499,7 @@ public function testBrave() : void { 'engine' => 'WebKit', 'engineversion' => '601.1.46', 'browser' => 'Brave', - 'browserversion' => '1.2.11', - 'browserreleased' => '2026-04-08' + 'browserversion' => '1.2.11' ], 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_4_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Brave/115.0.0.0 Safari/605.1.15' => [ 'string' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_4_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Brave/115.0.0.0 Safari/605.1.15', @@ -660,8 +659,7 @@ public function testKonqueror() : void { 'platform' => 'Kubuntu', 'browser' => 'Konqueror', 'browserversion' => '3.5', - 'language' => 'en-US', - 'browserreleased' => '2007-12-04' + 'language' => 'en-US' ], 'Mozilla/5.0 (compatible; Konqueror/3.1; i686 Linux; 20021102)' => [ 'string' => 'Mozilla/5.0 (compatible; Konqueror/3.1; i686 Linux; 20021102)', @@ -672,8 +670,7 @@ public function testKonqueror() : void { 'architecture' => 'x86', 'bits' => 32, 'browser' => 'Konqueror', - 'browserversion' => '3.1', - 'browserreleased' => '2007-12-04' + 'browserversion' => '3.1' ] ]; foreach ($strings AS $ua => $item) { @@ -1573,6 +1570,7 @@ public function testSilk() : void { 'bits' => 64, 'browser' => 'Silk', 'browserversion' => '148.0.5259.39', + 'browserreleased' => '2026-04-23', 'engine' => 'Blink', 'engineversion' => '148.0.5259.39' ] diff --git a/tests/clientHintsTest.php b/tests/clientHintsTest.php index fc992d7..276e299 100644 --- a/tests/clientHintsTest.php +++ b/tests/clientHintsTest.php @@ -24,7 +24,7 @@ public function testClientHints() : void { 'bits' => 64, 'kernel' => 'Windows NT', 'platform' => 'Windows', - 'platformversion' => '19.0.0', + 'platformversion' => '11', 'engine' => 'Blink', 'engineversion' => '133.0.6943.142', 'browser' => 'Chrome', From 199baa7eb96629806f2c0547156747569feb7410 Mon Sep 17 00:00:00 2001 From: Hexydec Date: Mon, 27 Apr 2026 16:52:32 +0100 Subject: [PATCH 2/3] Updated dependencies. --- composer.lock | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/composer.lock b/composer.lock index 9cde051..e195565 100644 --- a/composer.lock +++ b/composer.lock @@ -298,16 +298,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.5.5", + "version": "12.5.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "a25bde1f8f83849f441ef5713c6466e470872a71" + "reference": "876099a072646c7745f673d7aeab5382c4439691" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a25bde1f8f83849f441ef5713c6466e470872a71", - "reference": "a25bde1f8f83849f441ef5713c6466e470872a71", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/876099a072646c7745f673d7aeab5382c4439691", + "reference": "876099a072646c7745f673d7aeab5382c4439691", "shasum": "" }, "require": { @@ -362,7 +362,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.6" }, "funding": [ { @@ -382,7 +382,7 @@ "type": "tidelift" } ], - "time": "2026-04-13T04:53:32+00:00" + "time": "2026-04-15T08:23:17+00:00" }, { "name": "phpunit/php-file-iterator", @@ -643,16 +643,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.19", + "version": "12.5.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "92f7744ca5f5701c9e4b4a60d9e143f2d84956da" + "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/92f7744ca5f5701c9e4b4a60d9e143f2d84956da", - "reference": "92f7744ca5f5701c9e4b4a60d9e143f2d84956da", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969", + "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969", "shasum": "" }, "require": { @@ -666,15 +666,15 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.5", + "phpunit/php-code-coverage": "^12.5.6", "phpunit/php-file-iterator": "^6.0.1", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", "phpunit/php-timer": "^8.0.0", "sebastian/cli-parser": "^4.2.0", - "sebastian/comparator": "^7.1.5", + "sebastian/comparator": "^7.1.6", "sebastian/diff": "^7.0.0", - "sebastian/environment": "^8.0.4", + "sebastian/environment": "^8.1.0", "sebastian/exporter": "^7.0.2", "sebastian/global-state": "^8.0.2", "sebastian/object-enumerator": "^7.0.0", @@ -721,7 +721,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.23" }, "funding": [ { @@ -729,7 +729,7 @@ "type": "other" } ], - "time": "2026-04-13T05:38:19+00:00" + "time": "2026-04-18T06:12:49+00:00" }, { "name": "sebastian/cli-parser", @@ -1019,16 +1019,16 @@ }, { "name": "sebastian/environment", - "version": "8.0.4", + "version": "8.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11" + "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", - "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b121608b28a13f721e76ffbbd386d08eff58f3f6", + "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6", "shasum": "" }, "require": { @@ -1043,7 +1043,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "8.1-dev" } }, "autoload": { @@ -1071,7 +1071,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/8.0.4" + "source": "https://github.com/sebastianbergmann/environment/tree/8.1.0" }, "funding": [ { @@ -1091,7 +1091,7 @@ "type": "tidelift" } ], - "time": "2026-03-15T07:05:40+00:00" + "time": "2026-04-15T12:13:01+00:00" }, { "name": "sebastian/exporter", From ae12ce6f23dad0d3ae7697296a696eb3cf663610 Mon Sep 17 00:00:00 2001 From: Hexydec Date: Mon, 27 Apr 2026 16:54:01 +0100 Subject: [PATCH 3/3] Updated PHP version in test runner. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bca73ce..bc13c7a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: - name: Install Composer uses: php-actions/composer@v6 with: - php_version: 8.2 + php_version: 8.4 php_extensions: mbstring xdebug - name: PHPUnit Tests