xref: /plugin/statistics/vendor/matomo/device-detector/Parser/OperatingSystem.php (revision d5ef99ddb7dfb0cfae33e9257bd1d788f682c50f)
1*d5ef99ddSAndreas Gohr<?php
2*d5ef99ddSAndreas Gohr
3*d5ef99ddSAndreas Gohr/**
4*d5ef99ddSAndreas Gohr * Device Detector - The Universal Device Detection library for parsing User Agents
5*d5ef99ddSAndreas Gohr *
6*d5ef99ddSAndreas Gohr * @link https://matomo.org
7*d5ef99ddSAndreas Gohr *
8*d5ef99ddSAndreas Gohr * @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
9*d5ef99ddSAndreas Gohr */
10*d5ef99ddSAndreas Gohr
11*d5ef99ddSAndreas Gohrdeclare(strict_types=1);
12*d5ef99ddSAndreas Gohr
13*d5ef99ddSAndreas Gohrnamespace DeviceDetector\Parser;
14*d5ef99ddSAndreas Gohr
15*d5ef99ddSAndreas Gohruse DeviceDetector\ClientHints;
16*d5ef99ddSAndreas Gohr
17*d5ef99ddSAndreas Gohr/**
18*d5ef99ddSAndreas Gohr * Class OperatingSystem
19*d5ef99ddSAndreas Gohr *
20*d5ef99ddSAndreas Gohr * Parses the useragent for operating system information
21*d5ef99ddSAndreas Gohr *
22*d5ef99ddSAndreas Gohr * Detected operating systems can be found in self::$operatingSystems and /regexes/oss.yml
23*d5ef99ddSAndreas Gohr * This class also defined some operating system families and methods to get the family for a specific os
24*d5ef99ddSAndreas Gohr */
25*d5ef99ddSAndreas Gohrclass OperatingSystem extends AbstractParser
26*d5ef99ddSAndreas Gohr{
27*d5ef99ddSAndreas Gohr    /**
28*d5ef99ddSAndreas Gohr     * @var string
29*d5ef99ddSAndreas Gohr     */
30*d5ef99ddSAndreas Gohr    protected $fixtureFile = 'regexes/oss.yml';
31*d5ef99ddSAndreas Gohr
32*d5ef99ddSAndreas Gohr    /**
33*d5ef99ddSAndreas Gohr     * @var string
34*d5ef99ddSAndreas Gohr     */
35*d5ef99ddSAndreas Gohr    protected $parserName = 'os';
36*d5ef99ddSAndreas Gohr
37*d5ef99ddSAndreas Gohr    /**
38*d5ef99ddSAndreas Gohr     * Known operating systems mapped to their internal short codes
39*d5ef99ddSAndreas Gohr     *
40*d5ef99ddSAndreas Gohr     * @var array
41*d5ef99ddSAndreas Gohr     */
42*d5ef99ddSAndreas Gohr    protected static $operatingSystems = [
43*d5ef99ddSAndreas Gohr        'AIX' => 'AIX',
44*d5ef99ddSAndreas Gohr        'AND' => 'Android',
45*d5ef99ddSAndreas Gohr        'ADR' => 'Android TV',
46*d5ef99ddSAndreas Gohr        'ALP' => 'Alpine Linux',
47*d5ef99ddSAndreas Gohr        'AMZ' => 'Amazon Linux',
48*d5ef99ddSAndreas Gohr        'AMG' => 'AmigaOS',
49*d5ef99ddSAndreas Gohr        'ARM' => 'Armadillo OS',
50*d5ef99ddSAndreas Gohr        'ARO' => 'AROS',
51*d5ef99ddSAndreas Gohr        'ATV' => 'tvOS',
52*d5ef99ddSAndreas Gohr        'ARL' => 'Arch Linux',
53*d5ef99ddSAndreas Gohr        'AOS' => 'AOSC OS',
54*d5ef99ddSAndreas Gohr        'ASP' => 'ASPLinux',
55*d5ef99ddSAndreas Gohr        'AZU' => 'Azure Linux',
56*d5ef99ddSAndreas Gohr        'BTR' => 'BackTrack',
57*d5ef99ddSAndreas Gohr        'SBA' => 'Bada',
58*d5ef99ddSAndreas Gohr        'BYI' => 'Baidu Yi',
59*d5ef99ddSAndreas Gohr        'BEO' => 'BeOS',
60*d5ef99ddSAndreas Gohr        'BLB' => 'BlackBerry OS',
61*d5ef99ddSAndreas Gohr        'QNX' => 'BlackBerry Tablet OS',
62*d5ef99ddSAndreas Gohr        'PAN' => 'blackPanther OS',
63*d5ef99ddSAndreas Gohr        'BOS' => 'Bliss OS',
64*d5ef99ddSAndreas Gohr        'BMP' => 'Brew',
65*d5ef99ddSAndreas Gohr        'BSN' => 'BrightSignOS',
66*d5ef99ddSAndreas Gohr        'CAI' => 'Caixa Mágica',
67*d5ef99ddSAndreas Gohr        'CES' => 'CentOS',
68*d5ef99ddSAndreas Gohr        'CST' => 'CentOS Stream',
69*d5ef99ddSAndreas Gohr        'CLO' => 'Clear Linux OS',
70*d5ef99ddSAndreas Gohr        'CLR' => 'ClearOS Mobile',
71*d5ef99ddSAndreas Gohr        'COS' => 'Chrome OS',
72*d5ef99ddSAndreas Gohr        'CRS' => 'Chromium OS',
73*d5ef99ddSAndreas Gohr        'CHN' => 'China OS',
74*d5ef99ddSAndreas Gohr        'COL' => 'Coolita OS',
75*d5ef99ddSAndreas Gohr        'CYN' => 'CyanogenMod',
76*d5ef99ddSAndreas Gohr        'DEB' => 'Debian',
77*d5ef99ddSAndreas Gohr        'DEE' => 'Deepin',
78*d5ef99ddSAndreas Gohr        'DFB' => 'DragonFly',
79*d5ef99ddSAndreas Gohr        'DVK' => 'DVKBuntu',
80*d5ef99ddSAndreas Gohr        'ELE' => 'ElectroBSD',
81*d5ef99ddSAndreas Gohr        'EUL' => 'EulerOS',
82*d5ef99ddSAndreas Gohr        'FED' => 'Fedora',
83*d5ef99ddSAndreas Gohr        'FEN' => 'Fenix',
84*d5ef99ddSAndreas Gohr        'FOS' => 'Firefox OS',
85*d5ef99ddSAndreas Gohr        'FIR' => 'Fire OS',
86*d5ef99ddSAndreas Gohr        'FOR' => 'Foresight Linux',
87*d5ef99ddSAndreas Gohr        'FRE' => 'Freebox',
88*d5ef99ddSAndreas Gohr        'BSD' => 'FreeBSD',
89*d5ef99ddSAndreas Gohr        'FRI' => 'FRITZ!OS',
90*d5ef99ddSAndreas Gohr        'FYD' => 'FydeOS',
91*d5ef99ddSAndreas Gohr        'FUC' => 'Fuchsia',
92*d5ef99ddSAndreas Gohr        'GNT' => 'Gentoo',
93*d5ef99ddSAndreas Gohr        'GNX' => 'GENIX',
94*d5ef99ddSAndreas Gohr        'GEO' => 'GEOS',
95*d5ef99ddSAndreas Gohr        'GNS' => 'gNewSense',
96*d5ef99ddSAndreas Gohr        'GRI' => 'GridOS',
97*d5ef99ddSAndreas Gohr        'GTV' => 'Google TV',
98*d5ef99ddSAndreas Gohr        'HPX' => 'HP-UX',
99*d5ef99ddSAndreas Gohr        'HAI' => 'Haiku OS',
100*d5ef99ddSAndreas Gohr        'IPA' => 'iPadOS',
101*d5ef99ddSAndreas Gohr        'HAR' => 'HarmonyOS',
102*d5ef99ddSAndreas Gohr        'HAS' => 'HasCodingOS',
103*d5ef99ddSAndreas Gohr        'HEL' => 'HELIX OS',
104*d5ef99ddSAndreas Gohr        'IRI' => 'IRIX',
105*d5ef99ddSAndreas Gohr        'INF' => 'Inferno',
106*d5ef99ddSAndreas Gohr        'JME' => 'Java ME',
107*d5ef99ddSAndreas Gohr        'JOL' => 'Joli OS',
108*d5ef99ddSAndreas Gohr        'KOS' => 'KaiOS',
109*d5ef99ddSAndreas Gohr        'KAL' => 'Kali',
110*d5ef99ddSAndreas Gohr        'KAN' => 'Kanotix',
111*d5ef99ddSAndreas Gohr        'KIN' => 'KIN OS',
112*d5ef99ddSAndreas Gohr        'KNO' => 'Knoppix',
113*d5ef99ddSAndreas Gohr        'KTV' => 'KreaTV',
114*d5ef99ddSAndreas Gohr        'KBT' => 'Kubuntu',
115*d5ef99ddSAndreas Gohr        'LIN' => 'GNU/Linux',
116*d5ef99ddSAndreas Gohr        'LEA' => 'LeafOS',
117*d5ef99ddSAndreas Gohr        'LND' => 'LindowsOS',
118*d5ef99ddSAndreas Gohr        'LNS' => 'Linspire',
119*d5ef99ddSAndreas Gohr        'LEN' => 'Lineage OS',
120*d5ef99ddSAndreas Gohr        'LIR' => 'Liri OS',
121*d5ef99ddSAndreas Gohr        'LOO' => 'Loongnix',
122*d5ef99ddSAndreas Gohr        'LBT' => 'Lubuntu',
123*d5ef99ddSAndreas Gohr        'LOS' => 'Lumin OS',
124*d5ef99ddSAndreas Gohr        'LUN' => 'LuneOS',
125*d5ef99ddSAndreas Gohr        'VLN' => 'VectorLinux',
126*d5ef99ddSAndreas Gohr        'MAC' => 'Mac',
127*d5ef99ddSAndreas Gohr        'MAE' => 'Maemo',
128*d5ef99ddSAndreas Gohr        'MAG' => 'Mageia',
129*d5ef99ddSAndreas Gohr        'MDR' => 'Mandriva',
130*d5ef99ddSAndreas Gohr        'SMG' => 'MeeGo',
131*d5ef99ddSAndreas Gohr        'MET' => 'Meta Horizon',
132*d5ef99ddSAndreas Gohr        'MCD' => 'MocorDroid',
133*d5ef99ddSAndreas Gohr        'MON' => 'moonOS',
134*d5ef99ddSAndreas Gohr        'EZX' => 'Motorola EZX',
135*d5ef99ddSAndreas Gohr        'MIN' => 'Mint',
136*d5ef99ddSAndreas Gohr        'MLD' => 'MildWild',
137*d5ef99ddSAndreas Gohr        'MOR' => 'MorphOS',
138*d5ef99ddSAndreas Gohr        'NBS' => 'NetBSD',
139*d5ef99ddSAndreas Gohr        'MTK' => 'MTK / Nucleus',
140*d5ef99ddSAndreas Gohr        'MRE' => 'MRE',
141*d5ef99ddSAndreas Gohr        'NXT' => 'NeXTSTEP',
142*d5ef99ddSAndreas Gohr        'NWS' => 'NEWS-OS',
143*d5ef99ddSAndreas Gohr        'WII' => 'Nintendo',
144*d5ef99ddSAndreas Gohr        'NDS' => 'Nintendo Mobile',
145*d5ef99ddSAndreas Gohr        'NOV' => 'Nova',
146*d5ef99ddSAndreas Gohr        'OS2' => 'OS/2',
147*d5ef99ddSAndreas Gohr        'T64' => 'OSF1',
148*d5ef99ddSAndreas Gohr        'OBS' => 'OpenBSD',
149*d5ef99ddSAndreas Gohr        'OVS' => 'OpenVMS',
150*d5ef99ddSAndreas Gohr        'OVZ' => 'OpenVZ',
151*d5ef99ddSAndreas Gohr        'OWR' => 'OpenWrt',
152*d5ef99ddSAndreas Gohr        'OTV' => 'Opera TV',
153*d5ef99ddSAndreas Gohr        'ORA' => 'Oracle Linux',
154*d5ef99ddSAndreas Gohr        'ORD' => 'Ordissimo',
155*d5ef99ddSAndreas Gohr        'PAR' => 'Pardus',
156*d5ef99ddSAndreas Gohr        'PCL' => 'PCLinuxOS',
157*d5ef99ddSAndreas Gohr        'PIC' => 'PICO OS',
158*d5ef99ddSAndreas Gohr        'PLA' => 'Plasma Mobile',
159*d5ef99ddSAndreas Gohr        'PSP' => 'PlayStation Portable',
160*d5ef99ddSAndreas Gohr        'PS3' => 'PlayStation',
161*d5ef99ddSAndreas Gohr        'PVE' => 'Proxmox VE',
162*d5ef99ddSAndreas Gohr        'PUF' => 'Puffin OS',
163*d5ef99ddSAndreas Gohr        'PUR' => 'PureOS',
164*d5ef99ddSAndreas Gohr        'QTP' => 'Qtopia',
165*d5ef99ddSAndreas Gohr        'PIO' => 'Raspberry Pi OS',
166*d5ef99ddSAndreas Gohr        'RAS' => 'Raspbian',
167*d5ef99ddSAndreas Gohr        'RHT' => 'Red Hat',
168*d5ef99ddSAndreas Gohr        'RST' => 'Red Star',
169*d5ef99ddSAndreas Gohr        'RED' => 'RedOS',
170*d5ef99ddSAndreas Gohr        'REV' => 'Revenge OS',
171*d5ef99ddSAndreas Gohr        'RIS' => 'risingOS',
172*d5ef99ddSAndreas Gohr        'ROS' => 'RISC OS',
173*d5ef99ddSAndreas Gohr        'ROC' => 'Rocky Linux',
174*d5ef99ddSAndreas Gohr        'ROK' => 'Roku OS',
175*d5ef99ddSAndreas Gohr        'RSO' => 'Rosa',
176*d5ef99ddSAndreas Gohr        'ROU' => 'RouterOS',
177*d5ef99ddSAndreas Gohr        'REM' => 'Remix OS',
178*d5ef99ddSAndreas Gohr        'RRS' => 'Resurrection Remix OS',
179*d5ef99ddSAndreas Gohr        'REX' => 'REX',
180*d5ef99ddSAndreas Gohr        'RZD' => 'RazoDroiD',
181*d5ef99ddSAndreas Gohr        'RXT' => 'RTOS & Next',
182*d5ef99ddSAndreas Gohr        'SAB' => 'Sabayon',
183*d5ef99ddSAndreas Gohr        'SSE' => 'SUSE',
184*d5ef99ddSAndreas Gohr        'SAF' => 'Sailfish OS',
185*d5ef99ddSAndreas Gohr        'SCI' => 'Scientific Linux',
186*d5ef99ddSAndreas Gohr        'SEE' => 'SeewoOS',
187*d5ef99ddSAndreas Gohr        'SER' => 'SerenityOS',
188*d5ef99ddSAndreas Gohr        'SIR' => 'Sirin OS',
189*d5ef99ddSAndreas Gohr        'SLW' => 'Slackware',
190*d5ef99ddSAndreas Gohr        'SOS' => 'Solaris',
191*d5ef99ddSAndreas Gohr        'SBL' => 'Star-Blade OS',
192*d5ef99ddSAndreas Gohr        'SYL' => 'Syllable',
193*d5ef99ddSAndreas Gohr        'SYM' => 'Symbian',
194*d5ef99ddSAndreas Gohr        'SYS' => 'Symbian OS',
195*d5ef99ddSAndreas Gohr        'S40' => 'Symbian OS Series 40',
196*d5ef99ddSAndreas Gohr        'S60' => 'Symbian OS Series 60',
197*d5ef99ddSAndreas Gohr        'SY3' => 'Symbian^3',
198*d5ef99ddSAndreas Gohr        'TEN' => 'TencentOS',
199*d5ef99ddSAndreas Gohr        'TDX' => 'ThreadX',
200*d5ef99ddSAndreas Gohr        'TIZ' => 'Tizen',
201*d5ef99ddSAndreas Gohr        'TIV' => 'TiVo OS',
202*d5ef99ddSAndreas Gohr        'TOS' => 'TmaxOS',
203*d5ef99ddSAndreas Gohr        'TUR' => 'Turbolinux',
204*d5ef99ddSAndreas Gohr        'UBT' => 'Ubuntu',
205*d5ef99ddSAndreas Gohr        'ULT' => 'ULTRIX',
206*d5ef99ddSAndreas Gohr        'UOS' => 'UOS',
207*d5ef99ddSAndreas Gohr        'VID' => 'VIDAA',
208*d5ef99ddSAndreas Gohr        'VIZ' => 'ViziOS',
209*d5ef99ddSAndreas Gohr        'WAS' => 'watchOS',
210*d5ef99ddSAndreas Gohr        'WER' => 'Wear OS',
211*d5ef99ddSAndreas Gohr        'WTV' => 'WebTV',
212*d5ef99ddSAndreas Gohr        'WHS' => 'Whale OS',
213*d5ef99ddSAndreas Gohr        'WIN' => 'Windows',
214*d5ef99ddSAndreas Gohr        'WCE' => 'Windows CE',
215*d5ef99ddSAndreas Gohr        'WIO' => 'Windows IoT',
216*d5ef99ddSAndreas Gohr        'WMO' => 'Windows Mobile',
217*d5ef99ddSAndreas Gohr        'WPH' => 'Windows Phone',
218*d5ef99ddSAndreas Gohr        'WRT' => 'Windows RT',
219*d5ef99ddSAndreas Gohr        'WPO' => 'WoPhone',
220*d5ef99ddSAndreas Gohr        'XBX' => 'Xbox',
221*d5ef99ddSAndreas Gohr        'XBT' => 'Xubuntu',
222*d5ef99ddSAndreas Gohr        'YNS' => 'YunOS',
223*d5ef99ddSAndreas Gohr        'ZEN' => 'Zenwalk',
224*d5ef99ddSAndreas Gohr        'ZOR' => 'ZorinOS',
225*d5ef99ddSAndreas Gohr        'IOS' => 'iOS',
226*d5ef99ddSAndreas Gohr        'POS' => 'palmOS',
227*d5ef99ddSAndreas Gohr        'WEB' => 'Webian',
228*d5ef99ddSAndreas Gohr        'WOS' => 'webOS',
229*d5ef99ddSAndreas Gohr    ];
230*d5ef99ddSAndreas Gohr
231*d5ef99ddSAndreas Gohr    /**
232*d5ef99ddSAndreas Gohr     * Operating system families mapped to the short codes of the associated operating systems
233*d5ef99ddSAndreas Gohr     *
234*d5ef99ddSAndreas Gohr     * @var array
235*d5ef99ddSAndreas Gohr     */
236*d5ef99ddSAndreas Gohr    protected static $osFamilies = [
237*d5ef99ddSAndreas Gohr        'Android'               => [
238*d5ef99ddSAndreas Gohr            'AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR',
239*d5ef99ddSAndreas Gohr            'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS', 'WER', 'PIC', 'ARM',
240*d5ef99ddSAndreas Gohr            'HEL', 'BYI', 'RIS', 'PUF', 'LEA', 'MET',
241*d5ef99ddSAndreas Gohr        ],
242*d5ef99ddSAndreas Gohr        'AmigaOS'               => ['AMG', 'MOR', 'ARO'],
243*d5ef99ddSAndreas Gohr        'BlackBerry'            => ['BLB', 'QNX'],
244*d5ef99ddSAndreas Gohr        'Brew'                  => ['BMP'],
245*d5ef99ddSAndreas Gohr        'BeOS'                  => ['BEO', 'HAI'],
246*d5ef99ddSAndreas Gohr        'Chrome OS'             => ['COS', 'CRS', 'FYD', 'SEE'],
247*d5ef99ddSAndreas Gohr        'Firefox OS'            => ['FOS', 'KOS'],
248*d5ef99ddSAndreas Gohr        'Gaming Console'        => ['WII', 'PS3'],
249*d5ef99ddSAndreas Gohr        'Google TV'             => ['GTV'],
250*d5ef99ddSAndreas Gohr        'IBM'                   => ['OS2'],
251*d5ef99ddSAndreas Gohr        'iOS'                   => ['IOS', 'ATV', 'WAS', 'IPA'],
252*d5ef99ddSAndreas Gohr        'RISC OS'               => ['ROS'],
253*d5ef99ddSAndreas Gohr        'GNU/Linux'             => [
254*d5ef99ddSAndreas Gohr            'LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED',
255*d5ef99ddSAndreas Gohr            'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF',
256*d5ef99ddSAndreas Gohr            'ORD', 'TOS', 'RSO', 'DEE', 'FRE', 'MAG', 'FEN', 'CAI', 'PCL', 'HAS',
257*d5ef99ddSAndreas Gohr            'LOS', 'DVK', 'ROK', 'OWR', 'OTV', 'KTV', 'PUR', 'PLA', 'FUC', 'PAR',
258*d5ef99ddSAndreas Gohr            'FOR', 'MON', 'KAN', 'ZEN', 'LND', 'LNS', 'CHN', 'AMZ', 'TEN', 'CST',
259*d5ef99ddSAndreas Gohr            'NOV', 'ROU', 'ZOR', 'RED', 'KAL', 'ORA', 'VID', 'TIV', 'BSN', 'RAS',
260*d5ef99ddSAndreas Gohr            'UOS', 'PIO', 'FRI', 'LIR', 'WEB', 'SER', 'ASP', 'AOS', 'LOO', 'EUL',
261*d5ef99ddSAndreas Gohr            'SCI', 'ALP', 'CLO', 'ROC', 'OVZ', 'PVE', 'RST', 'EZX', 'GNS', 'JOL',
262*d5ef99ddSAndreas Gohr            'TUR', 'QTP', 'WPO', 'PAN', 'VIZ', 'AZU', 'COL',
263*d5ef99ddSAndreas Gohr        ],
264*d5ef99ddSAndreas Gohr        'Mac'                   => ['MAC'],
265*d5ef99ddSAndreas Gohr        'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'],
266*d5ef99ddSAndreas Gohr        'OpenVMS'               => ['OVS'],
267*d5ef99ddSAndreas Gohr        'Real-time OS'          => ['MTK', 'TDX', 'MRE', 'JME', 'REX', 'RXT'],
268*d5ef99ddSAndreas Gohr        'Other Mobile'          => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE', 'LUN', 'GEO'],
269*d5ef99ddSAndreas Gohr        'Symbian'               => ['SYM', 'SYS', 'SY3', 'S60', 'S40'],
270*d5ef99ddSAndreas Gohr        'Unix'                  => [
271*d5ef99ddSAndreas Gohr            'SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64',
272*d5ef99ddSAndreas Gohr            'INF', 'ELE', 'GNX', 'ULT', 'NWS', 'NXT', 'SBL',
273*d5ef99ddSAndreas Gohr        ],
274*d5ef99ddSAndreas Gohr        'WebTV'                 => ['WTV'],
275*d5ef99ddSAndreas Gohr        'Windows'               => ['WIN'],
276*d5ef99ddSAndreas Gohr        'Windows Mobile'        => ['WPH', 'WMO', 'WCE', 'WRT', 'WIO', 'KIN'],
277*d5ef99ddSAndreas Gohr        'Other Smart TV'        => ['WHS'],
278*d5ef99ddSAndreas Gohr    ];
279*d5ef99ddSAndreas Gohr
280*d5ef99ddSAndreas Gohr    /**
281*d5ef99ddSAndreas Gohr     * Contains a list of mappings from OS names we use to known client hint values
282*d5ef99ddSAndreas Gohr     *
283*d5ef99ddSAndreas Gohr     * @var array<string, array<string>>
284*d5ef99ddSAndreas Gohr     */
285*d5ef99ddSAndreas Gohr    protected static $clientHintMapping = [
286*d5ef99ddSAndreas Gohr        'GNU/Linux' => ['Linux'],
287*d5ef99ddSAndreas Gohr        'Mac'       => ['MacOS'],
288*d5ef99ddSAndreas Gohr    ];
289*d5ef99ddSAndreas Gohr
290*d5ef99ddSAndreas Gohr    /**
291*d5ef99ddSAndreas Gohr     * Operating system families that are known as desktop only
292*d5ef99ddSAndreas Gohr     *
293*d5ef99ddSAndreas Gohr     * @var array
294*d5ef99ddSAndreas Gohr     */
295*d5ef99ddSAndreas Gohr    protected static $desktopOsArray = [
296*d5ef99ddSAndreas Gohr        'AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS',
297*d5ef99ddSAndreas Gohr    ];
298*d5ef99ddSAndreas Gohr
299*d5ef99ddSAndreas Gohr    /**
300*d5ef99ddSAndreas Gohr     * Fire OS version mapping
301*d5ef99ddSAndreas Gohr     *
302*d5ef99ddSAndreas Gohr     * @var array
303*d5ef99ddSAndreas Gohr     */
304*d5ef99ddSAndreas Gohr    private $fireOsVersionMapping = [
305*d5ef99ddSAndreas Gohr        '11'    => '8',
306*d5ef99ddSAndreas Gohr        '10'    => '8',
307*d5ef99ddSAndreas Gohr        '9'     => '7',
308*d5ef99ddSAndreas Gohr        '7'     => '6',
309*d5ef99ddSAndreas Gohr        '5'     => '5',
310*d5ef99ddSAndreas Gohr        '4.4.3' => '4.5.1',
311*d5ef99ddSAndreas Gohr        '4.4.2' => '4',
312*d5ef99ddSAndreas Gohr        '4.2.2' => '3',
313*d5ef99ddSAndreas Gohr        '4.0.3' => '3',
314*d5ef99ddSAndreas Gohr        '4.0.2' => '3',
315*d5ef99ddSAndreas Gohr        '4'     => '2',
316*d5ef99ddSAndreas Gohr        '2'     => '1',
317*d5ef99ddSAndreas Gohr    ];
318*d5ef99ddSAndreas Gohr
319*d5ef99ddSAndreas Gohr    /**
320*d5ef99ddSAndreas Gohr     * Lineage OS version mapping
321*d5ef99ddSAndreas Gohr     *
322*d5ef99ddSAndreas Gohr     * @var array
323*d5ef99ddSAndreas Gohr     */
324*d5ef99ddSAndreas Gohr    private $lineageOsVersionMapping = [
325*d5ef99ddSAndreas Gohr        '15'    => '22',
326*d5ef99ddSAndreas Gohr        '14'    => '21',
327*d5ef99ddSAndreas Gohr        '13'    => '20.0',
328*d5ef99ddSAndreas Gohr        '12.1'  => '19.1',
329*d5ef99ddSAndreas Gohr        '12'    => '19.0',
330*d5ef99ddSAndreas Gohr        '11'    => '18.0',
331*d5ef99ddSAndreas Gohr        '10'    => '17.0',
332*d5ef99ddSAndreas Gohr        '9'     => '16.0',
333*d5ef99ddSAndreas Gohr        '8.1.0' => '15.1',
334*d5ef99ddSAndreas Gohr        '8.0.0' => '15.0',
335*d5ef99ddSAndreas Gohr        '7.1.2' => '14.1',
336*d5ef99ddSAndreas Gohr        '7.1.1' => '14.1',
337*d5ef99ddSAndreas Gohr        '7.0'   => '14.0',
338*d5ef99ddSAndreas Gohr        '6.0.1' => '13.0',
339*d5ef99ddSAndreas Gohr        '6.0'   => '13.0',
340*d5ef99ddSAndreas Gohr        '5.1.1' => '12.1',
341*d5ef99ddSAndreas Gohr        '5.0.2' => '12.0',
342*d5ef99ddSAndreas Gohr        '5.0'   => '12.0',
343*d5ef99ddSAndreas Gohr        '4.4.4' => '11.0',
344*d5ef99ddSAndreas Gohr        '4.3'   => '10.2',
345*d5ef99ddSAndreas Gohr        '4.2.2' => '10.1',
346*d5ef99ddSAndreas Gohr        '4.0.4' => '9.1.0',
347*d5ef99ddSAndreas Gohr    ];
348*d5ef99ddSAndreas Gohr
349*d5ef99ddSAndreas Gohr    /**
350*d5ef99ddSAndreas Gohr     * Returns all available operating systems
351*d5ef99ddSAndreas Gohr     *
352*d5ef99ddSAndreas Gohr     * @return array
353*d5ef99ddSAndreas Gohr     */
354*d5ef99ddSAndreas Gohr    public static function getAvailableOperatingSystems(): array
355*d5ef99ddSAndreas Gohr    {
356*d5ef99ddSAndreas Gohr        return self::$operatingSystems;
357*d5ef99ddSAndreas Gohr    }
358*d5ef99ddSAndreas Gohr
359*d5ef99ddSAndreas Gohr    /**
360*d5ef99ddSAndreas Gohr     * Returns all available operating system families
361*d5ef99ddSAndreas Gohr     *
362*d5ef99ddSAndreas Gohr     * @return array
363*d5ef99ddSAndreas Gohr     */
364*d5ef99ddSAndreas Gohr    public static function getAvailableOperatingSystemFamilies(): array
365*d5ef99ddSAndreas Gohr    {
366*d5ef99ddSAndreas Gohr        return self::$osFamilies;
367*d5ef99ddSAndreas Gohr    }
368*d5ef99ddSAndreas Gohr
369*d5ef99ddSAndreas Gohr    /**
370*d5ef99ddSAndreas Gohr     * Returns the os name and shot name
371*d5ef99ddSAndreas Gohr     *
372*d5ef99ddSAndreas Gohr     * @param string $name
373*d5ef99ddSAndreas Gohr     *
374*d5ef99ddSAndreas Gohr     * @return array
375*d5ef99ddSAndreas Gohr     */
376*d5ef99ddSAndreas Gohr    public static function getShortOsData(string $name): array
377*d5ef99ddSAndreas Gohr    {
378*d5ef99ddSAndreas Gohr        $short = 'UNK';
379*d5ef99ddSAndreas Gohr
380*d5ef99ddSAndreas Gohr        foreach (self::$operatingSystems as $osShort => $osName) {
381*d5ef99ddSAndreas Gohr            if (\strtolower($name) !== \strtolower($osName)) {
382*d5ef99ddSAndreas Gohr                continue;
383*d5ef99ddSAndreas Gohr            }
384*d5ef99ddSAndreas Gohr
385*d5ef99ddSAndreas Gohr            $name  = $osName;
386*d5ef99ddSAndreas Gohr            $short = $osShort;
387*d5ef99ddSAndreas Gohr
388*d5ef99ddSAndreas Gohr            break;
389*d5ef99ddSAndreas Gohr        }
390*d5ef99ddSAndreas Gohr
391*d5ef99ddSAndreas Gohr        return \compact('short', 'name');
392*d5ef99ddSAndreas Gohr    }
393*d5ef99ddSAndreas Gohr
394*d5ef99ddSAndreas Gohr    /**
395*d5ef99ddSAndreas Gohr     * @inheritdoc
396*d5ef99ddSAndreas Gohr     */
397*d5ef99ddSAndreas Gohr    public function parse(): ?array
398*d5ef99ddSAndreas Gohr    {
399*d5ef99ddSAndreas Gohr        $this->restoreUserAgentFromClientHints();
400*d5ef99ddSAndreas Gohr
401*d5ef99ddSAndreas Gohr        $osFromClientHints = $this->parseOsFromClientHints();
402*d5ef99ddSAndreas Gohr        $osFromUserAgent   = $this->parseOsFromUserAgent();
403*d5ef99ddSAndreas Gohr
404*d5ef99ddSAndreas Gohr        if (!empty($osFromClientHints['name'])) {
405*d5ef99ddSAndreas Gohr            $name    = $osFromClientHints['name'];
406*d5ef99ddSAndreas Gohr            $version = $osFromClientHints['version'];
407*d5ef99ddSAndreas Gohr
408*d5ef99ddSAndreas Gohr            // use version from user agent if non was provided in client hints, but os family from useragent matches
409*d5ef99ddSAndreas Gohr            if (empty($version)
410*d5ef99ddSAndreas Gohr                && self::getOsFamily($name) === self::getOsFamily($osFromUserAgent['name'])
411*d5ef99ddSAndreas Gohr            ) {
412*d5ef99ddSAndreas Gohr                $version = $osFromUserAgent['version'];
413*d5ef99ddSAndreas Gohr            }
414*d5ef99ddSAndreas Gohr
415*d5ef99ddSAndreas Gohr            // On Windows, version 0.0.0 can be either 7, 8 or 8.1
416*d5ef99ddSAndreas Gohr            if ('Windows' === $name && '0.0.0' === $version) {
417*d5ef99ddSAndreas Gohr                $version = ('10' === $osFromUserAgent['version']) ? '' : $osFromUserAgent['version'];
418*d5ef99ddSAndreas Gohr            }
419*d5ef99ddSAndreas Gohr
420*d5ef99ddSAndreas Gohr            // If the OS name detected from client hints matches the OS family from user agent
421*d5ef99ddSAndreas Gohr            // but the os name is another, we use the one from user agent, as it might be more detailed
422*d5ef99ddSAndreas Gohr            if (self::getOsFamily($osFromUserAgent['name']) === $name && $osFromUserAgent['name'] !== $name) {
423*d5ef99ddSAndreas Gohr                $name = $osFromUserAgent['name'];
424*d5ef99ddSAndreas Gohr
425*d5ef99ddSAndreas Gohr                if ('LeafOS' === $name || 'HarmonyOS' === $name) {
426*d5ef99ddSAndreas Gohr                    $version = '';
427*d5ef99ddSAndreas Gohr                }
428*d5ef99ddSAndreas Gohr
429*d5ef99ddSAndreas Gohr                if ('PICO OS' === $name) {
430*d5ef99ddSAndreas Gohr                    $version = $osFromUserAgent['version'];
431*d5ef99ddSAndreas Gohr                }
432*d5ef99ddSAndreas Gohr
433*d5ef99ddSAndreas Gohr                if ('Fire OS' === $name && !empty($osFromClientHints['version'])) {
434*d5ef99ddSAndreas Gohr                    $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
435*d5ef99ddSAndreas Gohr
436*d5ef99ddSAndreas Gohr                    $version = $this->fireOsVersionMapping[$version]
437*d5ef99ddSAndreas Gohr                        ?? $this->fireOsVersionMapping[$majorVersion] ?? '';
438*d5ef99ddSAndreas Gohr                }
439*d5ef99ddSAndreas Gohr            }
440*d5ef99ddSAndreas Gohr
441*d5ef99ddSAndreas Gohr            $short = $osFromClientHints['short_name'];
442*d5ef99ddSAndreas Gohr
443*d5ef99ddSAndreas Gohr            // Chrome OS is in some cases reported as Linux in client hints, we fix this only if the version matches
444*d5ef99ddSAndreas Gohr            if ('GNU/Linux' === $name
445*d5ef99ddSAndreas Gohr                && 'Chrome OS' === $osFromUserAgent['name']
446*d5ef99ddSAndreas Gohr                && $osFromClientHints['version'] === $osFromUserAgent['version']
447*d5ef99ddSAndreas Gohr            ) {
448*d5ef99ddSAndreas Gohr                $name  = $osFromUserAgent['name'];
449*d5ef99ddSAndreas Gohr                $short = $osFromUserAgent['short_name'];
450*d5ef99ddSAndreas Gohr            }
451*d5ef99ddSAndreas Gohr
452*d5ef99ddSAndreas Gohr            // Chrome OS is in some cases reported as Android in client hints
453*d5ef99ddSAndreas Gohr            if ('Android' === $name && 'Chrome OS' === $osFromUserAgent['name']) {
454*d5ef99ddSAndreas Gohr                $name    = $osFromUserAgent['name'];
455*d5ef99ddSAndreas Gohr                $version = '';
456*d5ef99ddSAndreas Gohr                $short   = $osFromUserAgent['short_name'];
457*d5ef99ddSAndreas Gohr            }
458*d5ef99ddSAndreas Gohr
459*d5ef99ddSAndreas Gohr            // Meta Horizon is reported as Linux in client hints
460*d5ef99ddSAndreas Gohr            if ('GNU/Linux' === $name && 'Meta Horizon' === $osFromUserAgent['name']) {
461*d5ef99ddSAndreas Gohr                $name  = $osFromUserAgent['name'];
462*d5ef99ddSAndreas Gohr                $short = $osFromUserAgent['short_name'];
463*d5ef99ddSAndreas Gohr            }
464*d5ef99ddSAndreas Gohr        } elseif (!empty($osFromUserAgent['name'])) {
465*d5ef99ddSAndreas Gohr            $name    = $osFromUserAgent['name'];
466*d5ef99ddSAndreas Gohr            $version = $osFromUserAgent['version'];
467*d5ef99ddSAndreas Gohr            $short   = $osFromUserAgent['short_name'];
468*d5ef99ddSAndreas Gohr        } else {
469*d5ef99ddSAndreas Gohr            return [];
470*d5ef99ddSAndreas Gohr        }
471*d5ef99ddSAndreas Gohr
472*d5ef99ddSAndreas Gohr        $platform    = $this->parsePlatform();
473*d5ef99ddSAndreas Gohr        $family      = self::getOsFamily($short);
474*d5ef99ddSAndreas Gohr        $androidApps = [
475*d5ef99ddSAndreas Gohr            'com.hisense.odinbrowser', 'com.seraphic.openinet.pre', 'com.appssppa.idesktoppcbrowser',
476*d5ef99ddSAndreas Gohr            'every.browser.inc',
477*d5ef99ddSAndreas Gohr        ];
478*d5ef99ddSAndreas Gohr
479*d5ef99ddSAndreas Gohr        if (null !== $this->clientHints) {
480*d5ef99ddSAndreas Gohr            if (\in_array($this->clientHints->getApp(), $androidApps) && 'Android' !== $name) {
481*d5ef99ddSAndreas Gohr                $name    = 'Android';
482*d5ef99ddSAndreas Gohr                $family  = 'Android';
483*d5ef99ddSAndreas Gohr                $short   = 'ADR';
484*d5ef99ddSAndreas Gohr                $version = '';
485*d5ef99ddSAndreas Gohr            }
486*d5ef99ddSAndreas Gohr
487*d5ef99ddSAndreas Gohr            if ('org.lineageos.jelly' === $this->clientHints->getApp() && 'Lineage OS' !== $name) {
488*d5ef99ddSAndreas Gohr                $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
489*d5ef99ddSAndreas Gohr
490*d5ef99ddSAndreas Gohr                $name    = 'Lineage OS';
491*d5ef99ddSAndreas Gohr                $family  = 'Android';
492*d5ef99ddSAndreas Gohr                $short   = 'LEN';
493*d5ef99ddSAndreas Gohr                $version = $this->lineageOsVersionMapping[$version]
494*d5ef99ddSAndreas Gohr                    ?? $this->lineageOsVersionMapping[$majorVersion] ?? '';
495*d5ef99ddSAndreas Gohr            }
496*d5ef99ddSAndreas Gohr
497*d5ef99ddSAndreas Gohr            if ('org.mozilla.tv.firefox' === $this->clientHints->getApp() && 'Fire OS' !== $name) {
498*d5ef99ddSAndreas Gohr                $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
499*d5ef99ddSAndreas Gohr
500*d5ef99ddSAndreas Gohr                $name    = 'Fire OS';
501*d5ef99ddSAndreas Gohr                $family  = 'Android';
502*d5ef99ddSAndreas Gohr                $short   = 'FIR';
503*d5ef99ddSAndreas Gohr                $version = $this->fireOsVersionMapping[$version] ?? $this->fireOsVersionMapping[$majorVersion] ?? '';
504*d5ef99ddSAndreas Gohr            }
505*d5ef99ddSAndreas Gohr        }
506*d5ef99ddSAndreas Gohr
507*d5ef99ddSAndreas Gohr        $return = [
508*d5ef99ddSAndreas Gohr            'name'       => $name,
509*d5ef99ddSAndreas Gohr            'short_name' => $short,
510*d5ef99ddSAndreas Gohr            'version'    => $version,
511*d5ef99ddSAndreas Gohr            'platform'   => $platform,
512*d5ef99ddSAndreas Gohr            'family'     => $family,
513*d5ef99ddSAndreas Gohr        ];
514*d5ef99ddSAndreas Gohr
515*d5ef99ddSAndreas Gohr        if (\in_array($return['name'], self::$operatingSystems)) {
516*d5ef99ddSAndreas Gohr            $return['short_name'] = \array_search($return['name'], self::$operatingSystems);
517*d5ef99ddSAndreas Gohr        }
518*d5ef99ddSAndreas Gohr
519*d5ef99ddSAndreas Gohr        return $return;
520*d5ef99ddSAndreas Gohr    }
521*d5ef99ddSAndreas Gohr
522*d5ef99ddSAndreas Gohr    /**
523*d5ef99ddSAndreas Gohr     * Returns the operating system family for the given operating system
524*d5ef99ddSAndreas Gohr     *
525*d5ef99ddSAndreas Gohr     * @param string $osLabel name or short name
526*d5ef99ddSAndreas Gohr     *
527*d5ef99ddSAndreas Gohr     * @return string|null If null, "Unknown"
528*d5ef99ddSAndreas Gohr     */
529*d5ef99ddSAndreas Gohr    public static function getOsFamily(string $osLabel): ?string
530*d5ef99ddSAndreas Gohr    {
531*d5ef99ddSAndreas Gohr        if (\in_array($osLabel, self::$operatingSystems)) {
532*d5ef99ddSAndreas Gohr            $osLabel = \array_search($osLabel, self::$operatingSystems);
533*d5ef99ddSAndreas Gohr        }
534*d5ef99ddSAndreas Gohr
535*d5ef99ddSAndreas Gohr        foreach (self::$osFamilies as $family => $labels) {
536*d5ef99ddSAndreas Gohr            if (\in_array($osLabel, $labels)) {
537*d5ef99ddSAndreas Gohr                return (string) $family;
538*d5ef99ddSAndreas Gohr            }
539*d5ef99ddSAndreas Gohr        }
540*d5ef99ddSAndreas Gohr
541*d5ef99ddSAndreas Gohr        return null;
542*d5ef99ddSAndreas Gohr    }
543*d5ef99ddSAndreas Gohr
544*d5ef99ddSAndreas Gohr    /**
545*d5ef99ddSAndreas Gohr     * Returns true if OS is desktop
546*d5ef99ddSAndreas Gohr     *
547*d5ef99ddSAndreas Gohr     * @param string $osName OS short name
548*d5ef99ddSAndreas Gohr     *
549*d5ef99ddSAndreas Gohr     * @return bool
550*d5ef99ddSAndreas Gohr     */
551*d5ef99ddSAndreas Gohr    public static function isDesktopOs(string $osName): bool
552*d5ef99ddSAndreas Gohr    {
553*d5ef99ddSAndreas Gohr        $osFamily = self::getOsFamily($osName);
554*d5ef99ddSAndreas Gohr
555*d5ef99ddSAndreas Gohr        return \in_array($osFamily, self::$desktopOsArray);
556*d5ef99ddSAndreas Gohr    }
557*d5ef99ddSAndreas Gohr
558*d5ef99ddSAndreas Gohr    /**
559*d5ef99ddSAndreas Gohr     * Returns the full name for the given short name
560*d5ef99ddSAndreas Gohr     *
561*d5ef99ddSAndreas Gohr     * @param string      $os
562*d5ef99ddSAndreas Gohr     * @param string|null $ver
563*d5ef99ddSAndreas Gohr     *
564*d5ef99ddSAndreas Gohr     * @return ?string
565*d5ef99ddSAndreas Gohr     */
566*d5ef99ddSAndreas Gohr    public static function getNameFromId(string $os, ?string $ver = null): ?string
567*d5ef99ddSAndreas Gohr    {
568*d5ef99ddSAndreas Gohr        if (\array_key_exists($os, self::$operatingSystems)) {
569*d5ef99ddSAndreas Gohr            $osFullName = self::$operatingSystems[$os];
570*d5ef99ddSAndreas Gohr
571*d5ef99ddSAndreas Gohr            return \trim($osFullName . ' ' . $ver);
572*d5ef99ddSAndreas Gohr        }
573*d5ef99ddSAndreas Gohr
574*d5ef99ddSAndreas Gohr        return null;
575*d5ef99ddSAndreas Gohr    }
576*d5ef99ddSAndreas Gohr
577*d5ef99ddSAndreas Gohr    /**
578*d5ef99ddSAndreas Gohr     * Returns the OS that can be safely detected from client hints
579*d5ef99ddSAndreas Gohr     *
580*d5ef99ddSAndreas Gohr     * @return array
581*d5ef99ddSAndreas Gohr     */
582*d5ef99ddSAndreas Gohr    protected function parseOsFromClientHints(): array
583*d5ef99ddSAndreas Gohr    {
584*d5ef99ddSAndreas Gohr        $name = $version = $short = '';
585*d5ef99ddSAndreas Gohr
586*d5ef99ddSAndreas Gohr        if ($this->clientHints instanceof ClientHints && $this->clientHints->getOperatingSystem()) {
587*d5ef99ddSAndreas Gohr            $hintName = $this->applyClientHintMapping($this->clientHints->getOperatingSystem());
588*d5ef99ddSAndreas Gohr
589*d5ef99ddSAndreas Gohr            foreach (self::$operatingSystems as $osShort => $osName) {
590*d5ef99ddSAndreas Gohr                if ($this->fuzzyCompare($hintName, $osName)) {
591*d5ef99ddSAndreas Gohr                    $name  = $osName;
592*d5ef99ddSAndreas Gohr                    $short = $osShort;
593*d5ef99ddSAndreas Gohr
594*d5ef99ddSAndreas Gohr                    break;
595*d5ef99ddSAndreas Gohr                }
596*d5ef99ddSAndreas Gohr            }
597*d5ef99ddSAndreas Gohr
598*d5ef99ddSAndreas Gohr            $version = $this->clientHints->getOperatingSystemVersion();
599*d5ef99ddSAndreas Gohr
600*d5ef99ddSAndreas Gohr            if ('Windows' === $name) {
601*d5ef99ddSAndreas Gohr                $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
602*d5ef99ddSAndreas Gohr                $minorVersion = (int) (\explode('.', $version, 2)[1] ?? '0');
603*d5ef99ddSAndreas Gohr
604*d5ef99ddSAndreas Gohr                if (0 === $majorVersion) {
605*d5ef99ddSAndreas Gohr                    $minorVersionMapping = [1 => '7', 2 => '8', 3 => '8.1'];
606*d5ef99ddSAndreas Gohr                    $version             = $minorVersionMapping[$minorVersion] ?? $version;
607*d5ef99ddSAndreas Gohr                } elseif ($majorVersion > 0 && $majorVersion < 11) {
608*d5ef99ddSAndreas Gohr                    $version = '10';
609*d5ef99ddSAndreas Gohr                } elseif ($majorVersion > 10) {
610*d5ef99ddSAndreas Gohr                    $version = '11';
611*d5ef99ddSAndreas Gohr                }
612*d5ef99ddSAndreas Gohr            }
613*d5ef99ddSAndreas Gohr
614*d5ef99ddSAndreas Gohr            // On Windows, version 0.0.0 can be either 7, 8 or 8.1, so we return 0.0.0
615*d5ef99ddSAndreas Gohr            if ('Windows' !== $name && '0.0.0' !== $version && 0 === (int) $version) {
616*d5ef99ddSAndreas Gohr                $version = '';
617*d5ef99ddSAndreas Gohr            }
618*d5ef99ddSAndreas Gohr        }
619*d5ef99ddSAndreas Gohr
620*d5ef99ddSAndreas Gohr        return [
621*d5ef99ddSAndreas Gohr            'name'       => $name,
622*d5ef99ddSAndreas Gohr            'short_name' => $short,
623*d5ef99ddSAndreas Gohr            'version'    => $this->buildVersion($version, []),
624*d5ef99ddSAndreas Gohr        ];
625*d5ef99ddSAndreas Gohr    }
626*d5ef99ddSAndreas Gohr
627*d5ef99ddSAndreas Gohr    /**
628*d5ef99ddSAndreas Gohr     * Returns the OS that can be detected from useragent
629*d5ef99ddSAndreas Gohr     *
630*d5ef99ddSAndreas Gohr     * @return array
631*d5ef99ddSAndreas Gohr     *
632*d5ef99ddSAndreas Gohr     * @throws \Exception
633*d5ef99ddSAndreas Gohr     */
634*d5ef99ddSAndreas Gohr    protected function parseOsFromUserAgent(): array
635*d5ef99ddSAndreas Gohr    {
636*d5ef99ddSAndreas Gohr        $osRegex = $matches = [];
637*d5ef99ddSAndreas Gohr        $name    = $version = $short = '';
638*d5ef99ddSAndreas Gohr
639*d5ef99ddSAndreas Gohr        foreach ($this->getRegexes() as $osRegex) {
640*d5ef99ddSAndreas Gohr            $matches = $this->matchUserAgent($osRegex['regex']);
641*d5ef99ddSAndreas Gohr
642*d5ef99ddSAndreas Gohr            if ($matches) {
643*d5ef99ddSAndreas Gohr                break;
644*d5ef99ddSAndreas Gohr            }
645*d5ef99ddSAndreas Gohr        }
646*d5ef99ddSAndreas Gohr
647*d5ef99ddSAndreas Gohr        if (!empty($matches)) {
648*d5ef99ddSAndreas Gohr            $name                                = $this->buildByMatch($osRegex['name'], $matches);
649*d5ef99ddSAndreas Gohr            ['name' => $name, 'short' => $short] = self::getShortOsData($name);
650*d5ef99ddSAndreas Gohr
651*d5ef99ddSAndreas Gohr            $version = \array_key_exists('version', $osRegex)
652*d5ef99ddSAndreas Gohr                ? $this->buildVersion((string) $osRegex['version'], $matches)
653*d5ef99ddSAndreas Gohr                : '';
654*d5ef99ddSAndreas Gohr
655*d5ef99ddSAndreas Gohr            foreach ($osRegex['versions'] ?? [] as $regex) {
656*d5ef99ddSAndreas Gohr                $matches = $this->matchUserAgent($regex['regex']);
657*d5ef99ddSAndreas Gohr
658*d5ef99ddSAndreas Gohr                if (!$matches) {
659*d5ef99ddSAndreas Gohr                    continue;
660*d5ef99ddSAndreas Gohr                }
661*d5ef99ddSAndreas Gohr
662*d5ef99ddSAndreas Gohr                if (\array_key_exists('name', $regex)) {
663*d5ef99ddSAndreas Gohr                    $name                                = $this->buildByMatch($regex['name'], $matches);
664*d5ef99ddSAndreas Gohr                    ['name' => $name, 'short' => $short] = self::getShortOsData($name);
665*d5ef99ddSAndreas Gohr                }
666*d5ef99ddSAndreas Gohr
667*d5ef99ddSAndreas Gohr                if (\array_key_exists('version', $regex)) {
668*d5ef99ddSAndreas Gohr                    $version = $this->buildVersion((string) $regex['version'], $matches);
669*d5ef99ddSAndreas Gohr                }
670*d5ef99ddSAndreas Gohr
671*d5ef99ddSAndreas Gohr                break;
672*d5ef99ddSAndreas Gohr            }
673*d5ef99ddSAndreas Gohr        }
674*d5ef99ddSAndreas Gohr
675*d5ef99ddSAndreas Gohr        return [
676*d5ef99ddSAndreas Gohr            'name'       => $name,
677*d5ef99ddSAndreas Gohr            'short_name' => $short,
678*d5ef99ddSAndreas Gohr            'version'    => $version,
679*d5ef99ddSAndreas Gohr        ];
680*d5ef99ddSAndreas Gohr    }
681*d5ef99ddSAndreas Gohr
682*d5ef99ddSAndreas Gohr    /**
683*d5ef99ddSAndreas Gohr     * Parse current UserAgent string for the operating system platform
684*d5ef99ddSAndreas Gohr     *
685*d5ef99ddSAndreas Gohr     * @return string
686*d5ef99ddSAndreas Gohr     */
687*d5ef99ddSAndreas Gohr    protected function parsePlatform(): string
688*d5ef99ddSAndreas Gohr    {
689*d5ef99ddSAndreas Gohr        // Use architecture from client hints if available
690*d5ef99ddSAndreas Gohr        if ($this->clientHints instanceof ClientHints && $this->clientHints->getArchitecture()) {
691*d5ef99ddSAndreas Gohr            $arch = \strtolower($this->clientHints->getArchitecture());
692*d5ef99ddSAndreas Gohr
693*d5ef99ddSAndreas Gohr            if (false !== \strpos($arch, 'arm')) {
694*d5ef99ddSAndreas Gohr                return 'ARM';
695*d5ef99ddSAndreas Gohr            }
696*d5ef99ddSAndreas Gohr
697*d5ef99ddSAndreas Gohr            if (false !== \strpos($arch, 'loongarch64')) {
698*d5ef99ddSAndreas Gohr                return 'LoongArch64';
699*d5ef99ddSAndreas Gohr            }
700*d5ef99ddSAndreas Gohr
701*d5ef99ddSAndreas Gohr            if (false !== \strpos($arch, 'mips')) {
702*d5ef99ddSAndreas Gohr                return 'MIPS';
703*d5ef99ddSAndreas Gohr            }
704*d5ef99ddSAndreas Gohr
705*d5ef99ddSAndreas Gohr            if (false !== \strpos($arch, 'sh4')) {
706*d5ef99ddSAndreas Gohr                return 'SuperH';
707*d5ef99ddSAndreas Gohr            }
708*d5ef99ddSAndreas Gohr
709*d5ef99ddSAndreas Gohr            if (false !== \strpos($arch, 'sparc64')) {
710*d5ef99ddSAndreas Gohr                return 'SPARC64';
711*d5ef99ddSAndreas Gohr            }
712*d5ef99ddSAndreas Gohr
713*d5ef99ddSAndreas Gohr            if (false !== \strpos($arch, 'x64')
714*d5ef99ddSAndreas Gohr                || (false !== \strpos($arch, 'x86') && '64' === $this->clientHints->getBitness())
715*d5ef99ddSAndreas Gohr            ) {
716*d5ef99ddSAndreas Gohr                return 'x64';
717*d5ef99ddSAndreas Gohr            }
718*d5ef99ddSAndreas Gohr
719*d5ef99ddSAndreas Gohr            if (false !== \strpos($arch, 'x86')) {
720*d5ef99ddSAndreas Gohr                return 'x86';
721*d5ef99ddSAndreas Gohr            }
722*d5ef99ddSAndreas Gohr        }
723*d5ef99ddSAndreas Gohr
724*d5ef99ddSAndreas Gohr        if ($this->matchUserAgent('arm[ _;)ev]|.*arm$|.*arm64|aarch64|Apple ?TV|Watch ?OS|Watch1,[12]')) {
725*d5ef99ddSAndreas Gohr            return 'ARM';
726*d5ef99ddSAndreas Gohr        }
727*d5ef99ddSAndreas Gohr
728*d5ef99ddSAndreas Gohr        if ($this->matchUserAgent('loongarch64')) {
729*d5ef99ddSAndreas Gohr            return 'LoongArch64';
730*d5ef99ddSAndreas Gohr        }
731*d5ef99ddSAndreas Gohr
732*d5ef99ddSAndreas Gohr        if ($this->matchUserAgent('mips')) {
733*d5ef99ddSAndreas Gohr            return 'MIPS';
734*d5ef99ddSAndreas Gohr        }
735*d5ef99ddSAndreas Gohr
736*d5ef99ddSAndreas Gohr        if ($this->matchUserAgent('sh4')) {
737*d5ef99ddSAndreas Gohr            return 'SuperH';
738*d5ef99ddSAndreas Gohr        }
739*d5ef99ddSAndreas Gohr
740*d5ef99ddSAndreas Gohr        if ($this->matchUserAgent('sparc64')) {
741*d5ef99ddSAndreas Gohr            return 'SPARC64';
742*d5ef99ddSAndreas Gohr        }
743*d5ef99ddSAndreas Gohr
744*d5ef99ddSAndreas Gohr        if ($this->matchUserAgent('64-?bit|WOW64|(?:Intel)?x64|WINDOWS_64|win64|.*amd64|.*x86_?64')) {
745*d5ef99ddSAndreas Gohr            return 'x64';
746*d5ef99ddSAndreas Gohr        }
747*d5ef99ddSAndreas Gohr
748*d5ef99ddSAndreas Gohr        if ($this->matchUserAgent('.*32bit|.*win32|(?:i[0-9]|x)86|i86pc')) {
749*d5ef99ddSAndreas Gohr            return 'x86';
750*d5ef99ddSAndreas Gohr        }
751*d5ef99ddSAndreas Gohr
752*d5ef99ddSAndreas Gohr        return '';
753*d5ef99ddSAndreas Gohr    }
754*d5ef99ddSAndreas Gohr}
755