diff --git a/.gitignore b/.gitignore index 30eb580..6fb3916 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor +/.phpunit.cache composer.lock .phpunit.result.cache .phpunit.cache/test-results diff --git a/composer.json b/composer.json index c8ee7da..a01cecd 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,9 @@ "illuminate/http": "^8.0|^9.0|^10.0|^11.0", "illuminate/view": "^8.0|^9.0|^10.0|^11.0", "jenssegers/agent": "^2.6", - "doctrine/dbal": "^3.0" + "jaybizzle/crawler-detect": "^1.2", + "doctrine/dbal": "^3.0", + "mobiledetect/mobiledetectlib": "^2.7.6" }, "autoload": { "psr-4": { diff --git a/src/Agent.php b/src/Agent.php new file mode 100644 index 0000000..8fe73e9 --- /dev/null +++ b/src/Agent.php @@ -0,0 +1,382 @@ + 'Macintosh', + ]; + + /** @var array */ + protected static $additionalOperatingSystems = [ + 'Windows' => 'Windows', + 'Windows NT' => 'Windows NT', + 'OS X' => 'Mac OS X', + 'Debian' => 'Debian', + 'Ubuntu' => 'Ubuntu', + 'Macintosh' => 'PPC', + 'OpenBSD' => 'OpenBSD', + 'Linux' => 'Linux', + 'ChromeOS' => 'CrOS', + ]; + + /** @var array */ + protected static $additionalBrowsers = [ + 'Opera Mini' => 'Opera Mini', + 'Opera' => 'Opera|OPR', + 'Edge' => 'Edge|Edg', + 'Coc Coc' => 'coc_coc_browser', + 'UCBrowser' => 'UCBrowser', + 'Vivaldi' => 'Vivaldi', + 'Chrome' => 'Chrome', + 'Firefox' => 'Firefox', + 'Safari' => 'Safari', + 'IE' => 'MSIE|IEMobile|MSIEMobile|Trident/[.0-9]+', + 'Netscape' => 'Netscape', + 'Mozilla' => 'Mozilla', + 'WeChat' => 'MicroMessenger', + ]; + + /** @var array */ + protected static $additionalProperties = [ + // Operating systems + 'Windows' => 'Windows NT [VER]', + 'Windows NT' => 'Windows NT [VER]', + 'OS X' => 'OS X [VER]', + 'BlackBerryOS' => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'], + 'AndroidOS' => 'Android [VER]', + 'ChromeOS' => 'CrOS x86_64 [VER]', + + // Browsers + 'Opera Mini' => 'Opera Mini/[VER]', + 'Opera' => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]', 'Opera [VER]'], + 'Netscape' => 'Netscape/[VER]', + 'Mozilla' => 'rv:[VER]', + 'IE' => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'rv:[VER]'], + 'Edge' => ['Edge/[VER]', 'Edg/[VER]'], + 'Vivaldi' => 'Vivaldi/[VER]', + 'Coc Coc' => 'coc_coc_browser/[VER]', + ]; + + /** @var CrawlerDetect */ + protected static $crawlerDetect; + + /** @return array */ + public static function getDetectionRulesExtended() + { + static $rules; + + if (! $rules) { + $rules = static::mergeRules( + static::$desktopDevices, // NEW + static::$phoneDevices, + static::$tabletDevices, + static::$operatingSystems, + static::$additionalOperatingSystems, // NEW + static::$browsers, + static::$additionalBrowsers, // NEW + static::$utilities + ); + } + + return $rules; + } + + public function getRules() + { + if ($this->detectionType === static::DETECTION_TYPE_EXTENDED) { + return static::getDetectionRulesExtended(); + } + + return static::getMobileDetectionRules(); + } + + /** @return CrawlerDetect */ + public function getCrawlerDetect() + { + if (static::$crawlerDetect === null) { + static::$crawlerDetect = new CrawlerDetect(); + } + + return static::$crawlerDetect; + } + + public static function getBrowsers() + { + return static::mergeRules( + static::$additionalBrowsers, + static::$browsers + ); + } + + public static function getOperatingSystems() + { + return static::mergeRules( + static::$operatingSystems, + static::$additionalOperatingSystems + ); + } + + public static function getPlatforms() + { + return static::mergeRules( + static::$operatingSystems, + static::$additionalOperatingSystems + ); + } + + public static function getDesktopDevices() + { + return static::$desktopDevices; + } + + public static function getProperties() + { + return static::mergeRules( + static::$additionalProperties, + static::$properties + ); + } + + /** + * @param string $acceptLanguage + * @return array + */ + public function languages($acceptLanguage = null) + { + if ($acceptLanguage === null) { + $acceptLanguage = $this->getHttpHeader('HTTP_ACCEPT_LANGUAGE'); + } + + if (! $acceptLanguage) { + return []; + } + + $languages = []; + + // Parse accept language string. + foreach (explode(',', $acceptLanguage) as $piece) { + $parts = explode(';', $piece); + $language = strtolower($parts[0]); + $priority = empty($parts[1]) ? 1. : floatval(str_replace('q=', '', $parts[1])); + + $languages[$language] = $priority; + } + + // Sort languages by priority. + arsort($languages); + + return array_keys($languages); + } + + /** + * @param array $rules + * @param string|null $userAgent + * @return string|bool + */ + protected function findDetectionRulesAgainstUA(array $rules, $userAgent = null) + { + // Loop given rules + foreach ($rules as $key => $regex) { + if (empty($regex)) { + continue; + } + + // Check match + if ($this->match($regex, $userAgent)) { + return $key ?: reset($this->matchesArray); + } + } + + return false; + } + + /** + * @param string|null $userAgent + * @return string|bool + */ + public function browser($userAgent = null) + { + return $this->findDetectionRulesAgainstUA(static::getBrowsers(), $userAgent); + } + + /** + * @param string|null $userAgent + * @return string|bool + */ + public function platform($userAgent = null) + { + return $this->findDetectionRulesAgainstUA(static::getPlatforms(), $userAgent); + } + + /** + * @param string|null $userAgent + * @return string|bool + */ + public function device($userAgent = null) + { + $rules = static::mergeRules( + static::getDesktopDevices(), + static::getPhoneDevices(), + static::getTabletDevices(), + static::getUtilities() + ); + + return $this->findDetectionRulesAgainstUA($rules, $userAgent); + } + + /** + * @param string|null $userAgent deprecated + * @param array $httpHeaders deprecated + * @return bool + */ + public function isDesktop($userAgent = null, $httpHeaders = null) + { + // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront' + if ($this->getUserAgent() === 'Amazon CloudFront') { + $cfHeaders = $this->getCfHeaders(); + + if (array_key_exists('HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER', $cfHeaders)) { + return $cfHeaders['HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER'] === 'true'; + } + } + + return ! $this->isMobile($userAgent, $httpHeaders) && ! $this->isTablet($userAgent, $httpHeaders) && ! $this->isRobot($userAgent); + } + + /** + * @param string|null $userAgent deprecated + * @param array $httpHeaders deprecated + * @return bool + */ + public function isPhone($userAgent = null, $httpHeaders = null) + { + return $this->isMobile($userAgent, $httpHeaders) && ! $this->isTablet($userAgent, $httpHeaders); + } + + /** + * @param string|null $userAgent + * @return string|bool + */ + public function robot($userAgent = null) + { + if ($this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent)) { + return ucfirst($this->getCrawlerDetect()->getMatches()); + } + + return false; + } + + /** + * @param string|null $userAgent + * @return bool + */ + public function isRobot($userAgent = null) + { + return $this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent); + } + + /** + * @param null $userAgent + * @param null $httpHeaders + * @return string + */ + public function deviceType($userAgent = null, $httpHeaders = null) + { + if ($this->isDesktop($userAgent, $httpHeaders)) { + return 'desktop'; + } elseif ($this->isPhone($userAgent, $httpHeaders)) { + return 'phone'; + } elseif ($this->isTablet($userAgent, $httpHeaders)) { + return 'tablet'; + } elseif ($this->isRobot($userAgent)) { + return 'robot'; + } + + return 'other'; + } + + public function version($propertyName, $type = self::VERSION_TYPE_STRING) + { + if (empty($propertyName)) { + return false; + } + + // set the $type to the default if we don't recognize the type + if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) { + $type = self::VERSION_TYPE_STRING; + } + + $properties = self::getProperties(); + + // Check if the property exists in the properties array. + if (true === isset($properties[$propertyName])) { + // Prepare the pattern to be matched. + // Make sure we always deal with an array (string is converted). + $properties[$propertyName] = (array) $properties[$propertyName]; + + foreach ($properties[$propertyName] as $propertyMatchString) { + if (is_array($propertyMatchString)) { + $propertyMatchString = implode('|', $propertyMatchString); + } + + $propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString); + + // Identify and extract the version. + preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match); + + if (false === empty($match[1])) { + $version = ($type === self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]); + + return $version; + } + } + } + + return false; + } + + /** + * @param array $all + * @return array + */ + protected static function mergeRules(...$all) + { + $merged = []; + + foreach ($all as $rules) { + foreach ($rules as $key => $value) { + if (empty($merged[$key])) { + $merged[$key] = $value; + } elseif (is_array($merged[$key])) { + $merged[$key][] = $value; + } else { + $merged[$key] .= '|'.$value; + } + } + } + + return $merged; + } + + /** @inheritdoc */ + public function __call($name, $arguments) + { + // Make sure the name starts with 'is', otherwise + if (strpos($name, 'is') !== 0) { + throw new BadMethodCallException("No such method exists: $name"); + } + + $this->setDetectionType(self::DETECTION_TYPE_EXTENDED); + + $key = substr($name, 2); + + return $this->matchUAAgainstKey($key); + } +} diff --git a/src/AnalyticsServiceProvider.php b/src/AnalyticsServiceProvider.php index 586f1d2..0ccdb25 100644 --- a/src/AnalyticsServiceProvider.php +++ b/src/AnalyticsServiceProvider.php @@ -8,11 +8,6 @@ class AnalyticsServiceProvider extends ServiceProvider { - /** - * Bootstrap any package services. - * - * @return void - */ public function boot(): void { if ($this->app->runningInConsole()) { @@ -51,11 +46,6 @@ protected function routeConfig(): array ]; } - /** - * Register any application services. - * - * @return void - */ public function register(): void { $this->mergeConfigFrom( diff --git a/src/Http/Middleware/Analytics.php b/src/Http/Middleware/Analytics.php index fa36aa4..4937d1f 100644 --- a/src/Http/Middleware/Analytics.php +++ b/src/Http/Middleware/Analytics.php @@ -2,13 +2,13 @@ namespace AndreasElia\Analytics\Http\Middleware; +use AndreasElia\Analytics\Agent; use AndreasElia\Analytics\Contracts\SessionProvider; use AndreasElia\Analytics\Models\PageView; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\App; -use Jenssegers\Agent\Agent; class Analytics { diff --git a/tests/Unit/.gitkeep b/tests/Unit/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Unit/AgentTest.php b/tests/Unit/AgentTest.php new file mode 100644 index 0000000..e699654 --- /dev/null +++ b/tests/Unit/AgentTest.php @@ -0,0 +1,296 @@ + 'Windows', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2' => 'OS X', + 'Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3' => 'iOS', + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0' => 'Ubuntu', + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+' => 'BlackBerryOS', + 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' => 'AndroidOS', + 'Mozilla/5.0 (X11; CrOS x86_64 6680.78.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.102 Safari/537.36' => 'ChromeOS', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' => 'Windows', + ]; + + private $browsers = [ + 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko' => 'IE', + 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25' => 'Safari', + 'Mozilla/5.0 (Windows; U; Win 9x 4.90; SG; rv:1.9.2.4) Gecko/20101104 Netscape/9.1.0285' => 'Netscape', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0' => 'Firefox', + 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36' => 'Chrome', + 'Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:2.2) Gecko/20110201' => 'Mozilla', + 'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14' => 'Opera', + 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36 OPR/27.0.1689.76' => 'Opera', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12' => 'Edge', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25' => 'Safari', + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36 Vivaldi/1.2.490.43' => 'Vivaldi', + 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-US; LT28h Build/6.1.E.3.7) AppleWebKit/534.31 (KHTML, like Gecko) UCBrowser/9.2.2.323 U3/0.8.0 Mobile Safari/534.31' => 'UCBrowser', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063' => 'Edge', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.29 Safari/537.36 Edg/79.0.309.18' => 'Edge', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) coc_coc_browser/86.0.180 Chrome/80.0.3987.180 Safari/537.36' => 'Coc Coc', + ]; + + private $robots = [ + 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' => 'Googlebot', + 'facebookexternalhit/1.1 (+http(s)://www.facebook.com/externalhit_uatext.php)' => 'Facebookexternalhit', + 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)' => 'Bingbot', + 'Twitterbot/1.0' => 'Twitterbot', + 'Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)' => 'Yandex', + ]; + + private $mobileDevices = [ + 'Mozilla/5.0 (iPhone; U; ru; CPU iPhone OS 4_2_1 like Mac OS X; ru) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148a Safari/6533.18.5' => 'iPhone', + 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25' => 'iPad', + 'Mozilla/5.0 (Linux; U; Android 2.3.4; fr-fr; HTC Desire Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' => 'HTC', + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+' => 'BlackBerry', + 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' => 'Nexus', + 'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; ASUS Transformer Pad TF300T Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30' => 'AsusTablet', + ]; + + private $desktopDevices = [ + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56' => 'Macintosh', + ]; + + private $browserVersions = [ + 'Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0' => '10.6', + 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko' => '11.0', + 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25' => '6.0', + 'Mozilla/5.0 (Windows; U; Win 9x 4.90; SG; rv:1.9.2.4) Gecko/20101104 Netscape/9.1.0285' => '9.1.0285', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0' => '25.0', + 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36' => '32.0.1667.0', + 'Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:2.2) Gecko/20110201' => '2.2', + 'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14' => '12.14', + 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51' => '11.51', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12' => '12', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.29 Safari/537.36 Edg/79.0.309.18' => '79.0.309.18', + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36 Vivaldi/1.2.490.43' => '1.2.490.43', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) coc_coc_browser/86.0.180 Chrome/80.0.3987.180 Safari/537.36' => '86.0.180', + ]; + + private $operatingSystemVersions = [ + 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko' => '6.3', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2' => '10_6_8', + 'Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3' => '5_1', + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+' => '7.1.0.346', + 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' => '2.2', + 'Mozilla/5.0 (X11; CrOS x86_64 6680.78.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.102 Safari/537.36' => '6680.78.0', + ]; + + private $desktops = [ + 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko', + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0', + 'Mozilla/5.0 (Windows; U; Win 9x 4.90; SG; rv:1.9.2.4) Gecko/20101104 Netscape/9.1.0285', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0', + 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36', + 'Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:2.2) Gecko/20110201', + 'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2', + ]; + + private $phones = [ + 'Mozilla/5.0 (iPhone; U; ru; CPU iPhone OS 4_2_1 like Mac OS X; ru) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148a Safari/6533.18.5', + 'Mozilla/5.0 (Linux; U; Android 2.3.4; fr-fr; HTC Desire Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+', + 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', + ]; + + #[Test] + public function languages() + { + $agent = new Agent(); + $agent->setHttpHeaders([ + 'HTTP_ACCEPT_LANGUAGE' => 'nl-NL,nl;q=0.8,en-US;q=0.6,en;q=0.4', + ]); + + $this->assertEquals(['nl-nl', 'nl', 'en-us', 'en'], $agent->languages()); + } + + #[Test] + public function languages_sorted() + { + $agent = new Agent(); + $agent->setHttpHeaders([ + 'HTTP_ACCEPT_LANGUAGE' => 'en;q=0.4,en-US,nl;q=0.6', + ]); + + $this->assertEquals(['en-us', 'nl', 'en'], $agent->languages()); + } + + #[Test] + public function operating_systems() + { + $agent = new Agent(); + + foreach ($this->operatingSystems as $ua => $platform) { + $agent->setUserAgent($ua); + $this->assertEquals($platform, $agent->platform(), $ua); + $this->assertTrue($agent->is($platform), $platform); + + if (! strpos($platform, ' ')) { + $method = "is{$platform}"; + $this->assertTrue($agent->{$method}(), $ua); + } + } + } + + #[Test] + public function browsers() + { + $agent = new Agent(); + + foreach ($this->browsers as $ua => $browser) { + $agent->setUserAgent($ua); + $this->assertEquals($browser, $agent->browser(), $ua); + $this->assertTrue($agent->is($browser), $browser); + + if (! strpos($browser, ' ')) { + $method = "is{$browser}"; + $this->assertTrue($agent->{$method}(), $ua); + } + } + } + + #[Test] + public function robots() + { + $agent = new Agent(); + + foreach ($this->robots as $ua => $robot) { + $agent->setUserAgent($ua); + $this->assertTrue($agent->isRobot(), $ua); + $this->assertEquals($robot, $agent->robot()); + } + } + + #[Test] + public function robot_should_return_false() + { + $agent = new Agent(); + + $this->assertFalse($agent->robot()); + } + + #[Test] + public function call_should_throw_bad_method_call_exception() + { + $this->expectException(\BadMethodCallException::class); + + $agent = new Agent(); + $agent->invalidMethod(); + } + + #[Test] + public function mobile_devices() + { + $agent = new Agent(); + + foreach ($this->mobileDevices as $ua => $device) { + $agent->setUserAgent($ua); + $this->assertEquals($device, $agent->device(), $ua); + $this->assertTrue($agent->isMobile(), $ua); + $this->assertFalse($agent->isDesktop(), $ua); + + if (! strpos($device, ' ')) { + $method = "is{$device}"; + $this->assertTrue($agent->{$method}(), $ua, $method); + } + } + } + + #[Test] + public function desktop_devices() + { + $agent = new Agent(); + + foreach ($this->desktopDevices as $ua => $device) { + $agent->setUserAgent($ua); + $this->assertEquals($device, $agent->device(), $ua); + $this->assertFalse($agent->isMobile(), $ua); + $this->assertTrue($agent->isDesktop(), $ua); + + if (! strpos($device, ' ')) { + $method = "is{$device}"; + $this->assertTrue($agent->{$method}(), $ua); + } + } + } + + #[Test] + public function versions() + { + $agent = new Agent(); + + foreach ($this->browserVersions as $ua => $version) { + $agent->setUserAgent($ua); + $browser = $agent->browser(); + $this->assertEquals($version, $agent->version($browser), $ua); + } + + foreach ($this->operatingSystemVersions as $ua => $version) { + $agent->setUserAgent($ua); + $platform = $agent->platform(); + $this->assertEquals($version, $agent->version($platform), $ua); + } + + foreach ($this->browsers as $ua => $browser) { + $agent->setUserAgent('FAKE'); + $this->assertFalse($agent->version($browser)); + } + } + + #[Test] + public function is_methods() + { + $agent = new Agent(); + + foreach ($this->desktops as $ua) { + $agent->setUserAgent($ua); + $this->assertTrue($agent->isDesktop(), $ua); + $this->assertFalse($agent->isMobile(), $ua); + $this->assertFalse($agent->isTablet(), $ua); + $this->assertFalse($agent->isPhone(), $ua); + $this->assertFalse($agent->isRobot(), $ua); + } + + foreach ($this->phones as $ua) { + $agent->setUserAgent($ua); + $this->assertTrue($agent->isPhone(), $ua); + $this->assertTrue($agent->isMobile(), $ua); + $this->assertFalse($agent->isDesktop(), $ua); + $this->assertFalse($agent->isTablet(), $ua); + $this->assertFalse($agent->isRobot(), $ua); + } + + foreach ($this->robots as $ua => $robot) { + $agent->setUserAgent($ua); + $this->assertTrue($agent->isRobot(), $ua); + $this->assertFalse($agent->isDesktop(), $ua); + $this->assertFalse($agent->isMobile(), $ua); + $this->assertFalse($agent->isTablet(), $ua); + $this->assertFalse($agent->isPhone(), $ua); + } + + foreach ($this->mobileDevices as $ua => $device) { + $agent->setUserAgent($ua); + $this->assertTrue($agent->isMobile(), $ua); + $this->assertFalse($agent->isDesktop(), $ua); + $this->assertFalse($agent->isRobot(), $ua); + } + + foreach ($this->desktopDevices as $ua => $device) { + $agent->setUserAgent($ua); + $this->assertTrue($agent->isDesktop(), $ua); + $this->assertFalse($agent->isMobile(), $ua); + $this->assertFalse($agent->isTablet(), $ua); + $this->assertFalse($agent->isPhone(), $ua); + $this->assertFalse($agent->isRobot(), $ua); + } + } +}