├── LICENSE.md ├── README.md ├── composer.json └── src ├── Agent.php ├── AgentServiceProvider.php └── Facades └── Agent.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jens Segers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Agent 2 | ===== 3 | 4 | [![Latest Stable Version](http://img.shields.io/packagist/v/jenssegers/agent.svg)](https://packagist.org/packages/jenssegers/agent) [![Total Downloads](http://img.shields.io/packagist/dm/jenssegers/agent.svg)](https://packagist.org/packages/jenssegers/agent) [![Build Status](http://img.shields.io/travis/jenssegers/agent.svg)](https://travis-ci.org/jenssegers/agent) [![Coverage Status](http://img.shields.io/coveralls/jenssegers/agent.svg)](https://coveralls.io/r/jenssegers/agent) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/jenssegers) 5 | 6 | A PHP desktop/mobile user agent parser with support for Laravel, based on [Mobile Detect](https://github.com/serbanghita/Mobile-Detect) with desktop support and additional functionality. 7 | 8 |

9 | 10 |

11 | 12 | Installation 13 | ------------ 14 | 15 | Install using composer: 16 | 17 | ```bash 18 | composer require jenssegers/agent 19 | ``` 20 | 21 | Laravel (optional) 22 | ------------------ 23 | 24 | Add the service provider in `config/app.php`: 25 | 26 | ```php 27 | Jenssegers\Agent\AgentServiceProvider::class, 28 | ``` 29 | 30 | And add the Agent alias to `config/app.php`: 31 | 32 | ```php 33 | 'Agent' => Jenssegers\Agent\Facades\Agent::class, 34 | ``` 35 | 36 | Basic Usage 37 | ----------- 38 | 39 | Start by creating an `Agent` instance (or use the `Agent` Facade if you are using Laravel): 40 | 41 | ```php 42 | use Jenssegers\Agent\Agent; 43 | 44 | $agent = new Agent(); 45 | ``` 46 | 47 | If you want to parse user agents other than the current request in CLI scripts for example, you can use the `setUserAgent` and `setHttpHeaders` methods: 48 | 49 | ```php 50 | $agent->setUserAgent('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'); 51 | $agent->setHttpHeaders($headers); 52 | ``` 53 | 54 | All of the original [Mobile Detect](https://github.com/serbanghita/Mobile-Detect) methods are still available, check out some original examples at https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples 55 | 56 | ### Is? 57 | 58 | Check for a certain property in the user agent. 59 | 60 | ```php 61 | $agent->is('Windows'); 62 | $agent->is('Firefox'); 63 | $agent->is('iPhone'); 64 | $agent->is('OS X'); 65 | ``` 66 | 67 | ### Magic is-method 68 | 69 | Magic method that does the same as the previous `is()` method: 70 | 71 | ```php 72 | $agent->isAndroidOS(); 73 | $agent->isNexus(); 74 | $agent->isSafari(); 75 | ``` 76 | 77 | ### Mobile detection 78 | 79 | Check for mobile device: 80 | 81 | ```php 82 | $agent->isMobile(); 83 | $agent->isTablet(); 84 | ``` 85 | 86 | ### Match user agent 87 | 88 | Search the user agent with a regular expression: 89 | 90 | ```php 91 | $agent->match('regexp'); 92 | ``` 93 | 94 | Additional Functionality 95 | ------------------------ 96 | 97 | ### Accept languages 98 | 99 | Get the browser's accept languages. Example: 100 | 101 | ```php 102 | $languages = $agent->languages(); 103 | // ['nl-nl', 'nl', 'en-us', 'en'] 104 | ``` 105 | 106 | ### Device name 107 | 108 | Get the device name, if mobile. (iPhone, Nexus, AsusTablet, ...) 109 | 110 | ```php 111 | $device = $agent->device(); 112 | ``` 113 | 114 | ### Operating system name 115 | 116 | Get the operating system. (Ubuntu, Windows, OS X, ...) 117 | 118 | ```php 119 | $platform = $agent->platform(); 120 | ``` 121 | 122 | ### Browser name 123 | 124 | Get the browser name. (Chrome, IE, Safari, Firefox, ...) 125 | 126 | ```php 127 | $browser = $agent->browser(); 128 | ``` 129 | 130 | ### Desktop detection 131 | 132 | Check if the user is using a desktop device. 133 | 134 | ```php 135 | $agent->isDesktop(); 136 | ``` 137 | 138 | *This checks if a user is not a mobile device, tablet or robot.* 139 | 140 | ### Phone detection 141 | 142 | Check if the user is using a phone device. 143 | 144 | ```php 145 | $agent->isPhone(); 146 | ``` 147 | 148 | ### Robot detection 149 | 150 | Check if the user is a robot. This uses [jaybizzle/crawler-detect](https://github.com/JayBizzle/Crawler-Detect) to do the actual robot detection. 151 | 152 | ```php 153 | $agent->isRobot(); 154 | ``` 155 | 156 | ### Robot name 157 | 158 | Get the robot name. 159 | 160 | ```php 161 | $robot = $agent->robot(); 162 | ``` 163 | 164 | ### Browser/platform version 165 | 166 | MobileDetect recently added a `version` method that can get the version number for components. To get the browser or platform version you can use: 167 | 168 | ```php 169 | $browser = $agent->browser(); 170 | $version = $agent->version($browser); 171 | 172 | $platform = $agent->platform(); 173 | $version = $agent->version($platform); 174 | ``` 175 | 176 | *Note, the version method is still in beta, so it might not return the correct result.* 177 | 178 | ## License 179 | 180 | Laravel User Agent is licensed under [The MIT License (MIT)](LICENSE). 181 | 182 | ## Security contact information 183 | 184 | To report a security vulnerability, follow [these steps](https://tidelift.com/security). 185 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jenssegers/agent", 3 | "description": "Desktop/mobile user agent parser with support for Laravel, based on Mobiledetect", 4 | "keywords": ["laravel", "useragent", "agent", "user agent", "browser", "platform", "mobile", "desktop"], 5 | "homepage": "https://github.com/jenssegers/agent", 6 | "license" : "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jens Segers", 10 | "homepage": "https://jenssegers.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.6", 15 | "mobiledetect/mobiledetectlib": "^2.7.6", 16 | "jaybizzle/crawler-detect": "^1.2" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^5.0|^6.0|^7.0", 20 | "php-coveralls/php-coveralls": "^2.1" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Jenssegers\\Agent\\": "src/" 25 | } 26 | }, 27 | "extra": { 28 | "branch-alias": { 29 | "dev-master": "3.0-dev" 30 | }, 31 | "laravel": { 32 | "providers": [ 33 | "Jenssegers\\Agent\\AgentServiceProvider" 34 | ], 35 | "aliases": { 36 | "Agent": "Jenssegers\\Agent\\Facades\\Agent" 37 | } 38 | } 39 | }, 40 | "suggest": { 41 | "illuminate/support": "Required for laravel service providers" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Agent.php: -------------------------------------------------------------------------------- 1 | 'Macintosh', 17 | ]; 18 | 19 | /** 20 | * List of additional operating systems. 21 | * @var array 22 | */ 23 | protected static $additionalOperatingSystems = [ 24 | 'Windows' => 'Windows', 25 | 'Windows NT' => 'Windows NT', 26 | 'OS X' => 'Mac OS X', 27 | 'Debian' => 'Debian', 28 | 'Ubuntu' => 'Ubuntu', 29 | 'Macintosh' => 'PPC', 30 | 'OpenBSD' => 'OpenBSD', 31 | 'Linux' => 'Linux', 32 | 'ChromeOS' => 'CrOS', 33 | ]; 34 | 35 | /** 36 | * List of additional browsers. 37 | * @var array 38 | */ 39 | protected static $additionalBrowsers = [ 40 | 'Opera Mini' => 'Opera Mini', 41 | 'Opera' => 'Opera|OPR', 42 | 'Edge' => 'Edge|Edg', 43 | 'Coc Coc' => 'coc_coc_browser', 44 | 'UCBrowser' => 'UCBrowser', 45 | 'Vivaldi' => 'Vivaldi', 46 | 'Chrome' => 'Chrome', 47 | 'Firefox' => 'Firefox', 48 | 'Safari' => 'Safari', 49 | 'IE' => 'MSIE|IEMobile|MSIEMobile|Trident/[.0-9]+', 50 | 'Netscape' => 'Netscape', 51 | 'Mozilla' => 'Mozilla', 52 | 'WeChat' => 'MicroMessenger', 53 | ]; 54 | 55 | /** 56 | * List of additional properties. 57 | * @var array 58 | */ 59 | protected static $additionalProperties = [ 60 | // Operating systems 61 | 'Windows' => 'Windows NT [VER]', 62 | 'Windows NT' => 'Windows NT [VER]', 63 | 'OS X' => 'OS X [VER]', 64 | 'BlackBerryOS' => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'], 65 | 'AndroidOS' => 'Android [VER]', 66 | 'ChromeOS' => 'CrOS x86_64 [VER]', 67 | 68 | // Browsers 69 | 'Opera Mini' => 'Opera Mini/[VER]', 70 | 'Opera' => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]', 'Opera [VER]'], 71 | 'Netscape' => 'Netscape/[VER]', 72 | 'Mozilla' => 'rv:[VER]', 73 | 'IE' => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'rv:[VER]'], 74 | 'Edge' => ['Edge/[VER]', 'Edg/[VER]'], 75 | 'Vivaldi' => 'Vivaldi/[VER]', 76 | 'Coc Coc' => 'coc_coc_browser/[VER]', 77 | ]; 78 | 79 | /** 80 | * @var CrawlerDetect 81 | */ 82 | protected static $crawlerDetect; 83 | 84 | /** 85 | * Get all detection rules. These rules include the additional 86 | * platforms and browsers and utilities. 87 | * @return array 88 | */ 89 | public static function getDetectionRulesExtended() 90 | { 91 | static $rules; 92 | 93 | if (!$rules) { 94 | $rules = static::mergeRules( 95 | static::$desktopDevices, // NEW 96 | static::$phoneDevices, 97 | static::$tabletDevices, 98 | static::$operatingSystems, 99 | static::$additionalOperatingSystems, // NEW 100 | static::$browsers, 101 | static::$additionalBrowsers, // NEW 102 | static::$utilities 103 | ); 104 | } 105 | 106 | return $rules; 107 | } 108 | 109 | public function getRules() 110 | { 111 | if ($this->detectionType === static::DETECTION_TYPE_EXTENDED) { 112 | return static::getDetectionRulesExtended(); 113 | } 114 | 115 | return static::getMobileDetectionRules(); 116 | } 117 | 118 | /** 119 | * @return CrawlerDetect 120 | */ 121 | public function getCrawlerDetect() 122 | { 123 | if (static::$crawlerDetect === null) { 124 | static::$crawlerDetect = new CrawlerDetect(); 125 | } 126 | 127 | return static::$crawlerDetect; 128 | } 129 | 130 | public static function getBrowsers() 131 | { 132 | return static::mergeRules( 133 | static::$additionalBrowsers, 134 | static::$browsers 135 | ); 136 | } 137 | 138 | public static function getOperatingSystems() 139 | { 140 | return static::mergeRules( 141 | static::$operatingSystems, 142 | static::$additionalOperatingSystems 143 | ); 144 | } 145 | 146 | public static function getPlatforms() 147 | { 148 | return static::mergeRules( 149 | static::$operatingSystems, 150 | static::$additionalOperatingSystems 151 | ); 152 | } 153 | 154 | public static function getDesktopDevices() 155 | { 156 | return static::$desktopDevices; 157 | } 158 | 159 | public static function getProperties() 160 | { 161 | return static::mergeRules( 162 | static::$additionalProperties, 163 | static::$properties 164 | ); 165 | } 166 | 167 | /** 168 | * Get accept languages. 169 | * @param string $acceptLanguage 170 | * @return array 171 | */ 172 | public function languages($acceptLanguage = null) 173 | { 174 | if ($acceptLanguage === null) { 175 | $acceptLanguage = $this->getHttpHeader('HTTP_ACCEPT_LANGUAGE'); 176 | } 177 | 178 | if (!$acceptLanguage) { 179 | return []; 180 | } 181 | 182 | $languages = []; 183 | 184 | // Parse accept language string. 185 | foreach (explode(',', $acceptLanguage) as $piece) { 186 | $parts = explode(';', $piece); 187 | $language = strtolower($parts[0]); 188 | $priority = empty($parts[1]) ? 1. : floatval(str_replace('q=', '', $parts[1])); 189 | 190 | $languages[$language] = $priority; 191 | } 192 | 193 | // Sort languages by priority. 194 | arsort($languages); 195 | 196 | return array_keys($languages); 197 | } 198 | 199 | /** 200 | * Match a detection rule and return the matched key. 201 | * @param array $rules 202 | * @param string|null $userAgent 203 | * @return string|bool 204 | */ 205 | protected function findDetectionRulesAgainstUA(array $rules, $userAgent = null) 206 | { 207 | // Loop given rules 208 | foreach ($rules as $key => $regex) { 209 | if (empty($regex)) { 210 | continue; 211 | } 212 | 213 | // Check match 214 | if ($this->match($regex, $userAgent)) { 215 | return $key ?: reset($this->matchesArray); 216 | } 217 | } 218 | 219 | return false; 220 | } 221 | 222 | /** 223 | * Get the browser name. 224 | * @param string|null $userAgent 225 | * @return string|bool 226 | */ 227 | public function browser($userAgent = null) 228 | { 229 | return $this->findDetectionRulesAgainstUA(static::getBrowsers(), $userAgent); 230 | } 231 | 232 | /** 233 | * Get the platform name. 234 | * @param string|null $userAgent 235 | * @return string|bool 236 | */ 237 | public function platform($userAgent = null) 238 | { 239 | return $this->findDetectionRulesAgainstUA(static::getPlatforms(), $userAgent); 240 | } 241 | 242 | /** 243 | * Get the device name. 244 | * @param string|null $userAgent 245 | * @return string|bool 246 | */ 247 | public function device($userAgent = null) 248 | { 249 | $rules = static::mergeRules( 250 | static::getDesktopDevices(), 251 | static::getPhoneDevices(), 252 | static::getTabletDevices(), 253 | static::getUtilities() 254 | ); 255 | 256 | return $this->findDetectionRulesAgainstUA($rules, $userAgent); 257 | } 258 | 259 | /** 260 | * Check if the device is a desktop computer. 261 | * @param string|null $userAgent deprecated 262 | * @param array $httpHeaders deprecated 263 | * @return bool 264 | */ 265 | public function isDesktop($userAgent = null, $httpHeaders = null) 266 | { 267 | // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront' 268 | if ($this->getUserAgent() === 'Amazon CloudFront') { 269 | $cfHeaders = $this->getCfHeaders(); 270 | if(array_key_exists('HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER', $cfHeaders)) { 271 | return $cfHeaders['HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER'] === 'true'; 272 | } 273 | } 274 | 275 | return !$this->isMobile($userAgent, $httpHeaders) && !$this->isTablet($userAgent, $httpHeaders) && !$this->isRobot($userAgent); 276 | } 277 | 278 | /** 279 | * Check if the device is a mobile phone. 280 | * @param string|null $userAgent deprecated 281 | * @param array $httpHeaders deprecated 282 | * @return bool 283 | */ 284 | public function isPhone($userAgent = null, $httpHeaders = null) 285 | { 286 | return $this->isMobile($userAgent, $httpHeaders) && !$this->isTablet($userAgent, $httpHeaders); 287 | } 288 | 289 | /** 290 | * Get the robot name. 291 | * @param string|null $userAgent 292 | * @return string|bool 293 | */ 294 | public function robot($userAgent = null) 295 | { 296 | if ($this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent)) { 297 | return ucfirst($this->getCrawlerDetect()->getMatches()); 298 | } 299 | 300 | return false; 301 | } 302 | 303 | /** 304 | * Check if device is a robot. 305 | * @param string|null $userAgent 306 | * @return bool 307 | */ 308 | public function isRobot($userAgent = null) 309 | { 310 | return $this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent); 311 | } 312 | 313 | /** 314 | * Get the device type 315 | * @param null $userAgent 316 | * @param null $httpHeaders 317 | * @return string 318 | */ 319 | public function deviceType($userAgent = null, $httpHeaders = null) 320 | { 321 | if ($this->isDesktop($userAgent, $httpHeaders)) { 322 | return "desktop"; 323 | } elseif ($this->isPhone($userAgent, $httpHeaders)) { 324 | return "phone"; 325 | } elseif ($this->isTablet($userAgent, $httpHeaders)) { 326 | return "tablet"; 327 | } elseif ($this->isRobot($userAgent)) { 328 | return "robot"; 329 | } 330 | 331 | return "other"; 332 | } 333 | 334 | public function version($propertyName, $type = self::VERSION_TYPE_STRING) 335 | { 336 | if (empty($propertyName)) { 337 | return false; 338 | } 339 | 340 | // set the $type to the default if we don't recognize the type 341 | if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) { 342 | $type = self::VERSION_TYPE_STRING; 343 | } 344 | 345 | $properties = self::getProperties(); 346 | 347 | // Check if the property exists in the properties array. 348 | if (true === isset($properties[$propertyName])) { 349 | 350 | // Prepare the pattern to be matched. 351 | // Make sure we always deal with an array (string is converted). 352 | $properties[$propertyName] = (array) $properties[$propertyName]; 353 | 354 | foreach ($properties[$propertyName] as $propertyMatchString) { 355 | if (is_array($propertyMatchString)) { 356 | $propertyMatchString = implode("|", $propertyMatchString); 357 | } 358 | 359 | $propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString); 360 | 361 | // Identify and extract the version. 362 | preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match); 363 | 364 | if (false === empty($match[1])) { 365 | $version = ($type === self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]); 366 | 367 | return $version; 368 | } 369 | } 370 | } 371 | 372 | return false; 373 | } 374 | 375 | /** 376 | * Merge multiple rules into one array. 377 | * @param array $all 378 | * @return array 379 | */ 380 | protected static function mergeRules(...$all) 381 | { 382 | $merged = []; 383 | 384 | foreach ($all as $rules) { 385 | foreach ($rules as $key => $value) { 386 | if (empty($merged[$key])) { 387 | $merged[$key] = $value; 388 | } elseif (is_array($merged[$key])) { 389 | $merged[$key][] = $value; 390 | } else { 391 | $merged[$key] .= '|' . $value; 392 | } 393 | } 394 | } 395 | 396 | return $merged; 397 | } 398 | 399 | /** 400 | * @inheritdoc 401 | */ 402 | public function __call($name, $arguments) 403 | { 404 | // Make sure the name starts with 'is', otherwise 405 | if (strpos($name, 'is') !== 0) { 406 | throw new BadMethodCallException("No such method exists: $name"); 407 | } 408 | 409 | $this->setDetectionType(self::DETECTION_TYPE_EXTENDED); 410 | 411 | $key = substr($name, 2); 412 | 413 | return $this->matchUAAgainstKey($key); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /src/AgentServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('agent', function ($app) { 22 | return new Agent($app['request']->server()); 23 | }); 24 | 25 | $this->app->alias('agent', Agent::class); 26 | } 27 | 28 | /** 29 | * Get the services provided by the provider. 30 | * 31 | * @return array 32 | */ 33 | public function provides() 34 | { 35 | return ['agent', Agent::class]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Facades/Agent.php: -------------------------------------------------------------------------------- 1 |