needle => label, most specific first */ private const BROWSERS = [ 'Edg/' => 'Edge', 'OPR/' => 'Opera', 'Vivaldi' => 'Vivaldi', 'Brave' => 'Brave', 'SamsungBrowser' => 'Samsung Internet', 'Firefox' => 'Firefox', 'CriOS' => 'Chrome', // Chrome on iOS, which is Safari underneath 'FxiOS' => 'Firefox', 'Chrome' => 'Chrome', 'Safari' => 'Safari', ]; /** @var array needle => label, most specific first */ private const PLATFORMS = [ 'iPhone' => 'iPhone', 'iPad' => 'iPad', 'Android' => 'Android', 'Macintosh' => 'macOS', 'Mac OS X' => 'macOS', 'Windows' => 'Windows', 'CrOS' => 'ChromeOS', 'Linux' => 'Linux', ]; public static function from(string $userAgent): string { $browser = self::match($userAgent, self::BROWSERS); $platform = self::match($userAgent, self::PLATFORMS); if ($browser !== null && $platform !== null) { return __('devices.name_on', ['browser' => $browser, 'platform' => $platform]); } // One of them is better than the shrug, and the shrug is better than a // raw user-agent string in a list somebody is meant to recognise // themselves in. return $browser ?? $platform ?? __('devices.name_unknown'); } /** @param array $map */ private static function match(string $haystack, array $map): ?string { foreach ($map as $needle => $label) { if (str_contains($haystack, $needle)) { return $label; } } return null; } }