diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd1e7d6..315a4dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,5 +28,5 @@ jobs: - name: Install dependencies with composer run: composer install - - name: Test with phpunit - run: vendor/bin/phpunit --coverage-text + - name: Run tests + run: make test diff --git a/.helpers/constants.php b/.helpers/constants.php new file mode 100644 index 0000000..dc750a9 --- /dev/null +++ b/.helpers/constants.php @@ -0,0 +1,15 @@ +getName()}`\n\n"; + +echo "| Constant | {$argv[2]} | \n|----------|----------| \n"; + +foreach( $class->getConstants() as $constant => $value ) { + echo "| `{$class->getShortName()}::{$constant}` | {$value} | \n"; +} + +echo "\n"; \ No newline at end of file diff --git a/.mddoc.xml b/.mddoc.xml new file mode 100644 index 0000000..d7d610e --- /dev/null +++ b/.mddoc.xml @@ -0,0 +1,108 @@ + + +
+ + + + + + +
+ +
+
+ +
+
+ +
+
+ + +
+ +
+
+ +
+
+
+ +
+
+ PHP User Agent is available through Packagist via Composer. + +
+
+ '[Detected Platform]', + 'browser' => '[Detected Browser]', + 'version' => '[Detected Browser Version]', +); +*/ +``` +]]> + parse(); +// or +$ua = $parser(); + +$ua->platform(); +$ua->browser(); +$ua->browserVersion(); +``` +]]> +
+
+ +
+
+ +
+ +
+
+
diff --git a/.travis.yml b/.travis.yml index 7e85a3d..79c1f59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,16 +3,16 @@ sudo: false dist: precise php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - 7.3 - - nightly - - hhvm + - "5.3" + - "5.4" + - "5.5" + - "5.6" + - "7.0" + - "7.1" + - "7.2" + - "7.3" + - "nightly" + - "hhvm" matrix: allow_failures: @@ -20,4 +20,4 @@ matrix: - php: hhvm install: composer install -script: vendor/bin/phpunit --coverage-clover=coverage.clover +script: make test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9281366 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: test +test: + ./vendor/bin/phpunit --coverage-text + +.PHONY: generate +generate: + php bin/user_agent_sorter.php > tests/user_agents.tmp.json && mv tests/user_agents.tmp.json tests/user_agents.json + php bin/constant_generator.php + +.PHONY: init +init: + php bin/init_user_agent.php > tests/user_agents.tmp.json && mv tests/user_agents.tmp.json tests/user_agents.json + make generate \ No newline at end of file diff --git a/README.md b/README.md index 30470f8..d2e5fe1 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,13 @@ [![Join the chat at https://gitter.im/PhpUserAgentParser/Lobby](https://badges.gitter.im/PhpUserAgentParser/Lobby.svg)](https://gitter.im/PhpUserAgentParser/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Latest Stable Version](https://poser.pugx.org/donatj/phpuseragentparser/v/stable.svg)](https://packagist.org/packages/donatj/phpuseragentparser) [![Total Downloads](https://poser.pugx.org/donatj/phpuseragentparser/downloads.svg)](https://packagist.org/packages/donatj/phpuseragentparser) [![Latest Unstable Version](https://poser.pugx.org/donatj/phpuseragentparser/v/unstable.svg)](https://packagist.org/packages/donatj/phpuseragentparser) [![License](https://poser.pugx.org/donatj/phpuseragentparser/license.svg)](https://packagist.org/packages/donatj/phpuseragentparser) -![CI](https://github.com/donatj/PhpUserAgent/workflows/CI/badge.svg) -[![Build Status](https://travis-ci.org/donatj/PhpUserAgent.svg?branch=master)](https://travis-ci.org/donatj/PhpUserAgent) + +[![Latest Stable Version](https://poser.pugx.org/donatj/phpuseragentparser/version)](https://packagist.org/packages/donatj/phpuseragentparser) +[![Total Downloads](https://poser.pugx.org/donatj/phpuseragentparser/downloads)](https://packagist.org/packages/donatj/phpuseragentparser) +[![License](https://poser.pugx.org/donatj/phpuseragentparser/license)](https://packagist.org/packages/donatj/phpuseragentparser) +[![Build Status](https://travis-ci.org/donatj/phpUserAgent.svg?branch=master)](https://travis-ci.org/donatj/phpUserAgent) +[![Build Status](https://github.com/donatj/phpUserAgent/workflows/CI/badge.svg?)](https://github.com/donatj/phpUserAgent/actions?query=workflow%3ACI) + ## What It Is @@ -12,10 +16,15 @@ A simple, streamlined PHP user-agent parser! Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php +## Upgrading to `1.*` + +The new `1.*` release **does not break compatibility** with `0.*` and nothing need to change to upgrade. However, the global `parse_user_agent` is now deprecated; it has been replaced with the namespaced `\donatj\UserAgent\parse_user_agent` and functions exactly the same. You can easily replace any existing call to `parse_user_agent` with `\donatj\UserAgent\parse_user_agent` + +In addition, 1.x adds a convenience object wrapper you may use should you prefer. More information on this is in the Usage section below. ## Why Use This -You have your choice in user-agent parsers. This one detects **all modern browsers** in a very light, quick, understandable fashion. +You have your choice in user-agent parsers. This one detects **all modern browsers** in a very light, quick, understandable fashion. It is less than 200 lines of code, and consists of just three regular expressions! It can also correctly identify exotic versions of IE others fail on. @@ -25,11 +34,12 @@ It offers 100% unit test coverage, is installable via Composer, and is very easy This is not meant as a browser "knowledge engine" but rather a simple parser. Anything not adequately provided directly by the user agent string itself will simply not be provided by this. + ### OS Versions User-agent strings **are not** a reliable source of OS Version! -- Many agents simply don't send the information. +- Many agents simply don't send the information. - Others provide varying levels of accuracy. - Parsing Windows versions alone almost nearly doubles the size of the code. @@ -39,25 +49,25 @@ All that said, there is the start of a [branch to do it](https://github.com/dona ### Undetectable Browsers -- **Brave** - Brave is simply not differentiable from Chrome. This was a design descision on their part. +- **Brave** - Brave is simply not differentiable from Chrome. This was a design decision on their part. ## Requirements - - PHP 5.3.0+ +- **php**: >=5.3.0 ## Installing PHP User Agent is available through Packagist via Composer. -```json -{ - "require": { - "donatj/phpuseragentparser": "*" - } -} +Install the latest version with: + +```bash +composer require 'donatj/phpuseragentparser' ``` -## Sample Usage +## Usage + +The classic procedural use is as simple as: ```php $ua_info = parse_user_agent(); @@ -70,59 +80,101 @@ array( */ ``` + +The new object oriented wrapper form: + +```php +$parser = new UserAgentParser(); + +$ua = $parser->parse(); +// or +$ua = $parser(); + +$ua->platform(); +$ua->browser(); +$ua->browserVersion(); +``` + ## Currently Detected Platforms -- Desktop - - Windows - - Linux - - Macintosh - - Chrome OS -- Mobile - - Android - - iPhone - - iPad / iPod Touch - - Windows Phone OS - - Kindle - - Kindle Fire - - BlackBerry - - Playbook - - Tizen -- Console - - Oculus - - Nintendo 3DS - - New Nintendo 3DS - - Nintendo Wii - - Nintendo WiiU - - PlayStation 3 - - PlayStation 4 - - PlayStation Vita - - Xbox 360 - - Xbox One +Predefined helper constants from `donatj\UserAgent\Platforms` + +| Constant | Platform | +|----------|----------| +| `Platforms::MACINTOSH` | Macintosh | +| `Platforms::CHROME_OS` | Chrome OS | +| `Platforms::LINUX` | Linux | +| `Platforms::WINDOWS` | Windows | +| `Platforms::ANDROID` | Android | +| `Platforms::BLACKBERRY` | BlackBerry | +| `Platforms::FREEBSD` | FreeBSD | +| `Platforms::IPAD` | iPad | +| `Platforms::IPHONE` | iPhone | +| `Platforms::IPOD` | iPod | +| `Platforms::KINDLE` | Kindle | +| `Platforms::KINDLE_FIRE` | Kindle Fire | +| `Platforms::NETBSD` | NetBSD | +| `Platforms::NEW_NINTENDO_3DS` | New Nintendo 3DS | +| `Platforms::NINTENDO_3DS` | Nintendo 3DS | +| `Platforms::NINTENDO_DS` | Nintendo DS | +| `Platforms::NINTENDO_SWITCH` | Nintendo Switch | +| `Platforms::NINTENDO_WII` | Nintendo Wii | +| `Platforms::NINTENDO_WIIU` | Nintendo WiiU | +| `Platforms::OPENBSD` | OpenBSD | +| `Platforms::PLAYBOOK` | PlayBook | +| `Platforms::PLAYSTATION_3` | PlayStation 3 | +| `Platforms::PLAYSTATION_4` | PlayStation 4 | +| `Platforms::PLAYSTATION_VITA` | PlayStation Vita | +| `Platforms::TIZEN` | Tizen | +| `Platforms::WINDOWS_PHONE` | Windows Phone | +| `Platforms::XBOX` | Xbox | +| `Platforms::XBOX_ONE` | Xbox One | ## Currently Detected Browsers -- Android Browser -- BlackBerry Browser -- Camino -- Kindle / Silk -- Firefox / IceWeasel / IceCat -- Safari -- Internet Explorer / Edge -- IEMobile -- Chrome / HeadlessChrome -- Yandex Browser -- Opera -- Midori -- Vivaldi -- TizenBrowser -- OculusBrowser -- SamsungBrowser -- UC Browser -- Lynx -- Wget -- Curl -- Puffin - - - -More information is available at [Donat Studios](http://donatstudios.com/PHP-Parser-HTTP_USER_AGENT). +Predefined helper constants from `donatj\UserAgent\Browsers` + +| Constant | Browser | +|----------|----------| +| `Browsers::ADSBOT_GOOGLE` | AdsBot-Google | +| `Browsers::ANDROID_BROWSER` | Android Browser | +| `Browsers::BAIDUSPIDER` | Baiduspider | +| `Browsers::BINGBOT` | bingbot | +| `Browsers::BLACKBERRY_BROWSER` | BlackBerry Browser | +| `Browsers::BROWSER` | Browser | +| `Browsers::BUNJALLOO` | Bunjalloo | +| `Browsers::CAMINO` | Camino | +| `Browsers::CHROME` | Chrome | +| `Browsers::CURL` | curl | +| `Browsers::EDGE` | Edge | +| `Browsers::FACEBOOKEXTERNALHIT` | facebookexternalhit | +| `Browsers::FEEDVALIDATOR` | FeedValidator | +| `Browsers::FIREFOX` | Firefox | +| `Browsers::GOOGLEBOT` | Googlebot | +| `Browsers::GOOGLEBOT_IMAGE` | Googlebot-Image | +| `Browsers::GOOGLEBOT_VIDEO` | Googlebot-Video | +| `Browsers::HEADLESSCHROME` | HeadlessChrome | +| `Browsers::IEMOBILE` | IEMobile | +| `Browsers::KINDLE` | Kindle | +| `Browsers::LYNX` | Lynx | +| `Browsers::MIDORI` | Midori | +| `Browsers::MSIE` | MSIE | +| `Browsers::MSNBOT_MEDIA` | msnbot-media | +| `Browsers::NETFRONT` | NetFront | +| `Browsers::NINTENDOBROWSER` | NintendoBrowser | +| `Browsers::OCULUSBROWSER` | OculusBrowser | +| `Browsers::OPERA` | Opera | +| `Browsers::PUFFIN` | Puffin | +| `Browsers::SAFARI` | Safari | +| `Browsers::SAMSUNGBROWSER` | SamsungBrowser | +| `Browsers::SILK` | Silk | +| `Browsers::TIZENBROWSER` | TizenBrowser | +| `Browsers::UC_BROWSER` | UC Browser | +| `Browsers::VALVE_STEAM_TENFOOT` | Valve Steam Tenfoot | +| `Browsers::VIVALDI` | Vivaldi | +| `Browsers::WGET` | Wget | +| `Browsers::WORDPRESS` | WordPress | +| `Browsers::YANDEX` | Yandex | +| `Browsers::YANDEXBOT` | YandexBot | + +More information is available at [Donat Studios](http://donatstudios.com/PHP-Parser-HTTP_USER_AGENT). \ No newline at end of file diff --git a/bin/constant_generator.php b/bin/constant_generator.php new file mode 100644 index 0000000..34c2540 --- /dev/null +++ b/bin/constant_generator.php @@ -0,0 +1,77 @@ + $val ) { + $kex = strtoupper($val['browser']); + if( $kex !== '' ) { + $kex = preg_replace('/\W+/', '_', $kex); + if( !isset($browsers[$kex][$val['browser']]) ) { + $browsers[$kex][$val['browser']] = 0; + } + + $browsers[$kex][$val['browser']]++; + } + + $kex = strtoupper($val['platform']); + if( $kex !== '' ) { + $kex = preg_replace('/\W+/', '_', $kex); + + if( !isset($platforms[$kex][$val['platform']]) ) { + $platforms[$kex][$val['platform']] = 0; + } + + $platforms[$kex][$val['platform']]++; + } +} + +ksort($browsers); +$file = basename(__FILE__); +$header = << $val ) { + $browserBody .= sprintf("\tconst %-{$maxKey}s = %s;\n", $const, var_export(key($val), true)); +} +$browserBody .= "\n}\n\n"; + +foreach( $platforms as $platform ) { + if( count($platform) !== 1 ) { + echo "bad platform count\n"; + die(2); + } +} + +$platformBody = "{$header}namespace donatj\UserAgent;\n\ninterface Platforms {\n\n"; +$maxKey = max(array_map('strlen', array_keys($platforms))); +foreach( $platforms as $const => $val ) { + $platformBody .= sprintf("\tconst %-{$maxKey}s = %s;\n", $const, var_export(key($val), true)); +} +$platformBody .= "\n}\n\n"; + +file_put_contents(__DIR__ . '/../src/UserAgent/Browsers.php', $browserBody); +file_put_contents(__DIR__ . '/../src/UserAgent/Platforms.php', $platformBody); \ No newline at end of file diff --git a/bin/user_agent_sorter.php b/bin/user_agent_sorter.php index d7ebfb4..07c6fb8 100644 --- a/bin/user_agent_sorter.php +++ b/bin/user_agent_sorter.php @@ -9,19 +9,29 @@ foreach( $uas as $key => &$val ) { $val['key'] = $key; } +unset($val); uasort($uas, function ( $a, $b ) { - if($a['platform'] === null && $b['platform'] !== null) return 1; - if($b['platform'] === null && $a['platform'] !== null) return -1; + if( $a['platform'] === null && $b['platform'] !== null ) { + return 1; + } + if( $b['platform'] === null && $a['platform'] !== null ) { + return -1; + } $desktop = array( 'Windows', 'Linux', 'Macintosh', 'Chrome OS' ); - $ad = in_array($a['platform'], $desktop); - $bd = in_array($b['platform'], $desktop); + $ad = in_array($a['platform'], $desktop, true); + $bd = in_array($b['platform'], $desktop, true); + + if( !$ad && $bd ) { + return 1; + } - if( !$ad && $bd ) return 1; - if( $ad && !$bd ) return -1; + if( $ad && !$bd ) { + return -1; + } if( $ad ) { $result = strnatcasecmp($a['browser'], $b['browser']); @@ -32,7 +42,6 @@ if( $result == 0 ) { $result = compare_version($a['version'], $b['version']); } - } } else { $result = strnatcasecmp($a['platform'], $b['platform']); @@ -43,7 +52,6 @@ if( $result == 0 ) { $result = compare_version($a['version'], $b['version']); } - } } @@ -57,11 +65,12 @@ foreach( $uas as &$val ) { unset($val['key']); } +unset($val); $jsonPretty = new Camspiers\JsonPretty\JsonPretty; + $json = $jsonPretty->prettify($uas) . "\n"; echo $json; -file_put_contents($jsonfile, $json); function compare_version( $a, $b ) { @@ -76,12 +85,12 @@ function compare_version( $a, $b ) { $aa = strtolower(isset($cmp_a[$i]) ? $cmp_a[$i] : '0'); $bb = strtolower(isset($cmp_b[$i]) ? $cmp_b[$i] : '0'); - if( is_numeric($aa) && is_numeric($bb) ) { - if( $aa != $bb ) { - $value = ($aa > $bb ? 1 : -1); - break; - } - } else if( $cmp = strcmp($aa, $bb) ) { + if( is_numeric($aa) && is_numeric($bb) && $aa !== $bb ) { + $value = ($aa > $bb ? 1 : -1); + break; + } + + if( $cmp = strcmp($aa, $bb) ) { $value = $cmp / abs($cmp); break; } diff --git a/composer.json b/composer.json index 4b07ee6..3a4246a 100644 --- a/composer.json +++ b/composer.json @@ -2,14 +2,20 @@ "name": "donatj/phpuseragentparser", "type": "library", "description": "Lightning fast, minimalist PHP UserAgent string parser.", - "keywords": ["user agent", "useragent", "parser", "browser", "browser detection"], + "keywords": [ + "user agent", + "useragent", + "parser", + "browser", + "browser detection" + ], "homepage": "http://donatstudios.com/PHP-Parser-HTTP_USER_AGENT", "license": "MIT", "require": { "php": ">=5.3.0" }, "require-dev": { - "camspiers/json-pretty": "0.1.*", + "camspiers/json-pretty": "~1.0", "phpunit/phpunit": "~4.8", "donatj/drop": "*" }, @@ -22,6 +28,11 @@ } ], "autoload": { - "files": [ "src/UserAgentParser.php" ] + "files": [ + "src/UserAgentParser.php" + ], + "psr-4": { + "donatj\\UserAgent\\": "src/UserAgent" + } } } diff --git a/src/UserAgent/Browsers.php b/src/UserAgent/Browsers.php new file mode 100644 index 0000000..99e3ca0 --- /dev/null +++ b/src/UserAgent/Browsers.php @@ -0,0 +1,51 @@ +platform = $platform; + $this->browser = $browser; + $this->browserVersion = $browserVersion; + } + + /** + * @return string|null + * @see \donatj\UserAgent\Platforms for a list of tested platforms + */ + public function platform() { + return $this->platform; + } + + /** + * @return string|null + * @see \donatj\UserAgent\Browsers for a list of tested browsers. + */ + public function browser() { + return $this->browser; + } + + /** + * The version string. Formatting depends on the browser. + * + * @return string|null + */ + public function browserVersion() { + return $this->browserVersion; + } +} diff --git a/src/UserAgent/UserAgentInterface.php b/src/UserAgent/UserAgentInterface.php new file mode 100644 index 0000000..645d62b --- /dev/null +++ b/src/UserAgent/UserAgentInterface.php @@ -0,0 +1,25 @@ +parse($u_agent); + } + +} diff --git a/src/UserAgentParser.php b/src/UserAgentParser.php index 6ba738c..ee5bc6f 100644 --- a/src/UserAgentParser.php +++ b/src/UserAgentParser.php @@ -1,177 +1,210 @@ * * @link https://donatstudios.com/PHP-Parser-HTTP_USER_AGENT * @link https://github.com/donatj/PhpUserAgent * - * @license MIT + * @license MIT https://github.com/donatj/PhpUserAgent/blob/master/LICENSE.md */ -function parse_user_agent( $u_agent = null ) { - if( $u_agent === null && isset($_SERVER['HTTP_USER_AGENT']) ) { - $u_agent = $_SERVER['HTTP_USER_AGENT']; - } - if( $u_agent === null ) { - throw new \InvalidArgumentException('parse_user_agent requires a user agent'); +namespace { + + /** + * Parses a user agent string into its important parts + * + * This method is defined for backwards comparability with the old global method. + * + * @param string|null $u_agent User agent string to parse or null. Uses $_SERVER['HTTP_USER_AGENT'] on NULL + * @return string[] an array with 'browser', 'version' and 'platform' keys + * @throws \InvalidArgumentException on not having a proper user agent to parse. + * + * @deprecated This exists for backwards compatibility with 0.x and will likely be removed in 2.x + * @see \donatj\UserAgent\parse_user_agent + */ + function parse_user_agent( $u_agent = null ) { + return \donatj\UserAgent\parse_user_agent($u_agent); } +} - $platform = null; - $browser = null; - $version = null; +namespace donatj\UserAgent { + + const PLATFORM = 'platform'; + const BROWSER = 'browser'; + const BROWSER_VERSION = 'version'; + + /** + * Parses a user agent string into its important parts + * + * @param string|null $u_agent User agent string to parse or null. Uses $_SERVER['HTTP_USER_AGENT'] on NULL + * @return string[] an array with 'browser', 'version' and 'platform' keys + * @throws \InvalidArgumentException on not having a proper user agent to parse. + */ + function parse_user_agent( $u_agent = null ) { + if( $u_agent === null && isset($_SERVER['HTTP_USER_AGENT']) ) { + $u_agent = (string)$_SERVER['HTTP_USER_AGENT']; + } - $empty = array( 'platform' => $platform, 'browser' => $browser, 'version' => $version ); + if( $u_agent === null ) { + throw new \InvalidArgumentException('parse_user_agent requires a user agent'); + } - if( !$u_agent ) { - return $empty; - } + $platform = null; + $browser = null; + $version = null; - if( preg_match('/\((.*?)\)/m', $u_agent, $parent_matches) ) { - preg_match_all('/(?PBB\d+;|Android|CrOS|Tizen|iPhone|iPad|iPod|Linux|(Open|Net|Free)BSD|Macintosh|Windows(\ Phone)?|Silk|linux-gnu|BlackBerry|PlayBook|X11|(New\ )?Nintendo\ (WiiU?|3?DS|Switch)|Xbox(\ One)?) - (?:\ [^;]*)? - (?:;|$)/imx', $parent_matches[1], $result); + $empty = array( PLATFORM => $platform, BROWSER => $browser, BROWSER_VERSION => $version ); - $priority = array( 'Xbox One', 'Xbox', 'Windows Phone', 'Tizen', 'Android', 'FreeBSD', 'NetBSD', 'OpenBSD', 'CrOS', 'X11' ); + if( !$u_agent ) { + return $empty; + } - $result['platform'] = array_unique($result['platform']); - if( count($result['platform']) > 1 ) { - if( $keys = array_intersect($priority, $result['platform']) ) { - $platform = reset($keys); - } else { - $platform = $result['platform'][0]; + if( preg_match('/\((.*?)\)/m', $u_agent, $parent_matches) ) { + preg_match_all(<<<'REGEX' +/(?PBB\d+;|Android|CrOS|Tizen|iPhone|iPad|iPod|Linux|(Open|Net|Free)BSD|Macintosh|Windows(\ Phone)?|Silk|linux-gnu|BlackBerry|PlayBook|X11|(New\ )?Nintendo\ (WiiU?|3?DS|Switch)|Xbox(\ One)?) +(?:\ [^;]*)? +(?:;|$)/imx +REGEX + , $parent_matches[1], $result); + + $priority = array( 'Xbox One', 'Xbox', 'Windows Phone', 'Tizen', 'Android', 'FreeBSD', 'NetBSD', 'OpenBSD', 'CrOS', 'X11' ); + + $result[PLATFORM] = array_unique($result[PLATFORM]); + if( count($result[PLATFORM]) > 1 ) { + if( $keys = array_intersect($priority, $result[PLATFORM]) ) { + $platform = reset($keys); + } else { + $platform = $result[PLATFORM][0]; + } + } elseif( isset($result[PLATFORM][0]) ) { + $platform = $result[PLATFORM][0]; } - } elseif( isset($result['platform'][0]) ) { - $platform = $result['platform'][0]; } - } - - if( $platform == 'linux-gnu' || $platform == 'X11' ) { - $platform = 'Linux'; - } elseif( $platform == 'CrOS' ) { - $platform = 'Chrome OS'; - } - preg_match_all('%(?PCamino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit| - TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|Edge|Edg|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser| - Baiduspider|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl| - Valve\ Steam\ Tenfoot| - NintendoBrowser|PLAYSTATION\ (\d|Vita)+) - (?:\)?;?) - (?:(?:[:/ ])(?P[0-9A-Z.]+)|/(?:[A-Z]*))%ix', - $u_agent, $result); - - // If nothing matched, return null (to avoid undefined index errors) - if( !isset($result['browser'][0]) || !isset($result['version'][0]) ) { - if( preg_match('%^(?!Mozilla)(?P[A-Z0-9\-]+)(/(?P[0-9A-Z.]+))?%ix', $u_agent, $result) ) { - return array( 'platform' => $platform ?: null, 'browser' => $result['browser'], 'version' => isset($result['version']) ? $result['version'] ?: null : null ); + if( $platform == 'linux-gnu' || $platform == 'X11' ) { + $platform = 'Linux'; + } elseif( $platform == 'CrOS' ) { + $platform = 'Chrome OS'; } - return $empty; - } + preg_match_all(<<<'REGEX' +%(?PCamino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit| +TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|Edge|Edg|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser| +Baiduspider|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl| +Valve\ Steam\ Tenfoot| +NintendoBrowser|PLAYSTATION\ (\d|Vita)+) +(?:\)?;?) +(?:(?:[:/ ])(?P[0-9A-Z.]+)|/(?:[A-Z]*))%ix +REGEX + , $u_agent, $result); + + // If nothing matched, return null (to avoid undefined index errors) + if( !isset($result[BROWSER][0]) || !isset($result[BROWSER_VERSION][0]) ) { + if( preg_match('%^(?!Mozilla)(?P[A-Z0-9\-]+)(/(?P[0-9A-Z.]+))?%ix', $u_agent, $result) ) { + return array( PLATFORM => $platform ?: null, BROWSER => $result[BROWSER], BROWSER_VERSION => empty($result[BROWSER_VERSION]) ? null : $result[BROWSER_VERSION] ); + } - if( preg_match('/rv:(?P[0-9A-Z.]+)/i', $u_agent, $rv_result) ) { - $rv_result = $rv_result['version']; - } + return $empty; + } - $browser = $result['browser'][0]; - $version = $result['version'][0]; + if( preg_match('/rv:(?P[0-9A-Z.]+)/i', $u_agent, $rv_result) ) { + $rv_result = $rv_result[BROWSER_VERSION]; + } - $lowerBrowser = array_map('strtolower', $result['browser']); + $browser = $result[BROWSER][0]; + $version = $result[BROWSER_VERSION][0]; - $find = function ( $search, &$key = null, &$value = null ) use ( $lowerBrowser ) { - $search = (array)$search; + $lowerBrowser = array_map('strtolower', $result[BROWSER]); - foreach( $search as $val ) { - $xkey = array_search(strtolower($val), $lowerBrowser); - if( $xkey !== false ) { - $value = $val; - $key = $xkey; + $find = function ( $search, &$key = null, &$value = null ) use ( $lowerBrowser ) { + $search = (array)$search; - return true; + foreach( $search as $val ) { + $xkey = array_search(strtolower($val), $lowerBrowser); + if( $xkey !== false ) { + $value = $val; + $key = $xkey; + + return true; + } } - } - return false; - }; + return false; + }; - $findT = function ( array $search, &$key = null, &$value = null ) use ( $find ) { - $value2 = null; - if( $find(array_keys($search), $key, $value2) ) { - $value = $search[$value2]; + $findT = function ( array $search, &$key = null, &$value = null ) use ( $find ) { + $value2 = null; + if( $find(array_keys($search), $key, $value2) ) { + $value = $search[$value2]; - return true; - } + return true; + } - return false; - }; - - $key = 0; - $val = ''; - if( $findT(array( 'OPR' => 'Opera', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge' ), $key, $browser) ) { - $version = $result['version'][$key]; - }elseif( $find('Playstation Vita', $key, $platform) ) { - $platform = 'PlayStation Vita'; - $browser = 'Browser'; - } elseif( $find(array( 'Kindle Fire', 'Silk' ), $key, $val) ) { - $browser = $val == 'Silk' ? 'Silk' : 'Kindle'; - $platform = 'Kindle Fire'; - if( !($version = $result['version'][$key]) || !is_numeric($version[0]) ) { - $version = $result['version'][array_search('Version', $result['browser'])]; - } - } elseif( $find('NintendoBrowser', $key) || $platform == 'Nintendo 3DS' ) { - $browser = 'NintendoBrowser'; - $version = $result['version'][$key]; - } elseif( $find('Kindle', $key, $platform) ) { - $browser = $result['browser'][$key]; - $version = $result['version'][$key]; - } elseif( $find('Opera', $key, $browser) ) { - $find('Version', $key); - $version = $result['version'][$key]; - } elseif( $find('Puffin', $key, $browser) ) { - $version = $result['version'][$key]; - if( strlen($version) > 3 ) { - $part = substr($version, -2); - if( ctype_upper($part) ) { - $version = substr($version, 0, -2); - - $flags = array( 'IP' => 'iPhone', 'IT' => 'iPad', 'AP' => 'Android', 'AT' => 'Android', 'WP' => 'Windows Phone', 'WT' => 'Windows' ); - if( isset($flags[$part]) ) { - $platform = $flags[$part]; + return false; + }; + + $key = 0; + $val = ''; + if( $findT(array( 'OPR' => 'Opera', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge' ), $key, $browser) ) { + $version = $result[BROWSER_VERSION][$key]; + } elseif( $find('Playstation Vita', $key, $platform) ) { + $platform = 'PlayStation Vita'; + $browser = 'Browser'; + } elseif( $find(array( 'Kindle Fire', 'Silk' ), $key, $val) ) { + $browser = $val == 'Silk' ? 'Silk' : 'Kindle'; + $platform = 'Kindle Fire'; + if( !($version = $result[BROWSER_VERSION][$key]) || !is_numeric($version[0]) ) { + $version = $result[BROWSER_VERSION][array_search('Version', $result[BROWSER])]; + } + } elseif( $find('NintendoBrowser', $key) || $platform == 'Nintendo 3DS' ) { + $browser = 'NintendoBrowser'; + $version = $result[BROWSER_VERSION][$key]; + } elseif( $find('Kindle', $key, $platform) ) { + $browser = $result[BROWSER][$key]; + $version = $result[BROWSER_VERSION][$key]; + } elseif( $find('Opera', $key, $browser) ) { + $find('Version', $key); + $version = $result[BROWSER_VERSION][$key]; + } elseif( $find('Puffin', $key, $browser) ) { + $version = $result[BROWSER_VERSION][$key]; + if( strlen($version) > 3 ) { + $part = substr($version, -2); + if( ctype_upper($part) ) { + $version = substr($version, 0, -2); + + $flags = array( 'IP' => 'iPhone', 'IT' => 'iPad', 'AP' => 'Android', 'AT' => 'Android', 'WP' => 'Windows Phone', 'WT' => 'Windows' ); + if( isset($flags[$part]) ) { + $platform = $flags[$part]; + } } } - } - } elseif( $find(array( 'IEMobile', 'Edge', 'Midori', 'Vivaldi', 'OculusBrowser', 'SamsungBrowser', 'Valve Steam Tenfoot', 'Chrome', 'HeadlessChrome' ), $key, $browser) ) { - $version = $result['version'][$key]; - } elseif( $rv_result && $find('Trident') ) { - $browser = 'MSIE'; - $version = $rv_result; - } elseif( $browser == 'AppleWebKit' ) { - if( $platform == 'Android' ) { - $browser = 'Android Browser'; - } elseif( strpos($platform, 'BB') === 0 ) { - $browser = 'BlackBerry Browser'; - $platform = 'BlackBerry'; - } elseif( $platform == 'BlackBerry' || $platform == 'PlayBook' ) { - $browser = 'BlackBerry Browser'; - } else { - $find('Safari', $key, $browser) || $find('TizenBrowser', $key, $browser); - } + } elseif( $find(array( 'IEMobile', 'Edge', 'Midori', 'Vivaldi', 'OculusBrowser', 'SamsungBrowser', 'Valve Steam Tenfoot', 'Chrome', 'HeadlessChrome' ), $key, $browser) ) { + $version = $result[BROWSER_VERSION][$key]; + } elseif( $rv_result && $find('Trident') ) { + $browser = 'MSIE'; + $version = $rv_result; + } elseif( $browser == 'AppleWebKit' ) { + if( $platform == 'Android' ) { + $browser = 'Android Browser'; + } elseif( strpos($platform, 'BB') === 0 ) { + $browser = 'BlackBerry Browser'; + $platform = 'BlackBerry'; + } elseif( $platform == 'BlackBerry' || $platform == 'PlayBook' ) { + $browser = 'BlackBerry Browser'; + } else { + $find('Safari', $key, $browser) || $find('TizenBrowser', $key, $browser); + } - $find('Version', $key); - $version = $result['version'][$key]; - } elseif( $pKey = preg_grep('/playstation \d/i', $result['browser']) ) { - $pKey = reset($pKey); + $find('Version', $key); + $version = $result[BROWSER_VERSION][$key]; + } elseif( $pKey = preg_grep('/playstation \d/i', $result[BROWSER]) ) { + $pKey = reset($pKey); - $platform = 'PlayStation ' . preg_replace('/\D/', '', $pKey); - $browser = 'NetFront'; - } + $platform = 'PlayStation ' . preg_replace('/\D/', '', $pKey); + $browser = 'NetFront'; + } - return array( 'platform' => $platform ?: null, 'browser' => $browser ?: null, 'version' => $version ?: null ); + return array( PLATFORM => $platform ?: null, BROWSER => $browser ?: null, BROWSER_VERSION => $version ?: null ); + } } diff --git a/tests/UserAgentParserTest.php b/tests/UserAgentParserFunctionTest.php similarity index 94% rename from tests/UserAgentParserTest.php rename to tests/UserAgentParserFunctionTest.php index 73930f4..7bb5b34 100644 --- a/tests/UserAgentParserTest.php +++ b/tests/UserAgentParserFunctionTest.php @@ -1,6 +1,6 @@ $parts ) { + $out[] = array( $string ); + } + + return $out; + } + + /** + * @dataProvider userAgentDataProvider + */ + public function test_parse( $string ) { + $parser = new UserAgentParser; + $result = $parser->parse($string); + + $expected = parse_user_agent($string); + + $this->assertSame($expected[\donatj\UserAgent\PLATFORM], $result->platform()); + $this->assertSame($expected[\donatj\UserAgent\BROWSER], $result->browser()); + $this->assertSame($expected[\donatj\UserAgent\BROWSER_VERSION], $result->browserVersion()); + } + + /** + * @dataProvider userAgentDataProvider + */ + public function test_invoke( $string ) { + $parser = new UserAgentParser; + $result = $parser($string); + + $expected = parse_user_agent($string); + + $this->assertSame($expected[\donatj\UserAgent\PLATFORM], $result->platform()); + $this->assertSame($expected[\donatj\UserAgent\BROWSER], $result->browser()); + $this->assertSame($expected[\donatj\UserAgent\BROWSER_VERSION], $result->browserVersion()); + } + +}