*/ protected static array $additionalOperatingSystems = [ 'Windows' => 'Windows', 'Windows NT' => 'Windows NT', 'OS X' => 'Mac OS X', 'Debian' => 'Debian', 'Ubuntu' => 'Ubuntu', 'Macintosh' => 'PPC', 'OpenBSD' => 'OpenBSD', 'Linux' => 'Linux', 'ChromeOS' => 'CrOS', ]; /** * List of additional browsers. * * @var array */ protected static array $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', ]; /** * Key value store for resolved strings. * * @var array */ protected array $store = []; /** * Get the platform name from the User Agent. */ public function platform(): ?string { return $this->retrieveUsingCacheOrResolve('platform', function () { return $this->findDetectionRulesAgainstUserAgent( $this->mergeRules(MobileDetect::getOperatingSystems(), static::$additionalOperatingSystems) ); }); } /** * Get the browser name from the User Agent. */ public function browser(): ?string { return $this->retrieveUsingCacheOrResolve('browser', function (): ?string { return $this->findDetectionRulesAgainstUserAgent( $this->mergeRules(static::$additionalBrowsers, MobileDetect::getBrowsers()) ); }); } /** * Determine if the device is a desktop computer. */ public function isDesktop(): bool { return $this->retrieveUsingCacheOrResolve('desktop', function (): bool { // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront' if ( $this->getUserAgent() === static::$cloudFrontUA && $this->getHttpHeader('HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER') === 'true' ) { return true; } return ! $this->isMobile() && ! $this->isTablet(); }); } /** * Match a detection rule and return the matched key. * * @param array> $rules */ protected function findDetectionRulesAgainstUserAgent(array $rules): ?string { $userAgent = $this->getUserAgent(); foreach ($rules as $key => $regex) { if (is_array($regex)) { $regex = implode('|', $regex); } if (empty($regex)) { continue; } if ($this->match($regex, $userAgent)) { if ($key !== '') { return $key; } $match = reset($this->matchesArray); return is_string($match) ? $match : null; } } return null; } /** * Retrieve from the given key from the cache or resolve the value. * * @template TReturn of string|bool|null * * @param Closure():TReturn $callback * @return TReturn */ protected function retrieveUsingCacheOrResolve(string $key, Closure $callback): string|bool|null { $cacheKey = $this->createCacheKey($key); if (! is_null($cacheItem = $this->store[$cacheKey] ?? null)) { return $cacheItem; } return tap(call_user_func($callback), function ($result) use ($cacheKey): void { $this->store[$cacheKey] = $result; }); } /** * Merge multiple rules into one array. * * @param array> ...$all * @return array */ protected function mergeRules(array ...$all): array { $merged = []; foreach ($all as $rules) { foreach ($rules as $key => $value) { $value = is_array($value) ? implode('|', $value) : $value; if (empty($merged[$key])) { $merged[$key] = $value; } else { $merged[$key] .= '|'.$value; } } } return $merged; } }