PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.9.2
Independent Analytics – WordPress Analytics Plugin v2.9.2
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / vendor / matomo / device-detector / Parser / OperatingSystem.php
independent-analytics / vendor / matomo / device-detector / Parser Last commit date
Client 2 years ago Device 2 years ago AbstractBotParser.php 2 years ago AbstractParser.php 2 years ago Bot.php 2 years ago OperatingSystem.php 2 years ago VendorFragment.php 2 years ago
OperatingSystem.php
353 lines
1 <?php
2
3 /**
4 * Device Detector - The Universal Device Detection library for parsing User Agents
5 *
6 * @link https://matomo.org
7 *
8 * @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
9 */
10 declare (strict_types=1);
11 namespace IAWPSCOPED\DeviceDetector\Parser;
12
13 use IAWPSCOPED\DeviceDetector\ClientHints;
14 /**
15 * Class OperatingSystem
16 *
17 * Parses the useragent for operating system information
18 *
19 * Detected operating systems can be found in self::$operatingSystems and /regexes/oss.yml
20 * This class also defined some operating system families and methods to get the family for a specific os
21 * @internal
22 */
23 class OperatingSystem extends AbstractParser
24 {
25 /**
26 * @var string
27 */
28 protected $fixtureFile = 'regexes/oss.yml';
29 /**
30 * @var string
31 */
32 protected $parserName = 'os';
33 /**
34 * Known operating systems mapped to their internal short codes
35 *
36 * @var array
37 */
38 protected static $operatingSystems = ['AIX' => 'AIX', 'AND' => 'Android', 'ADR' => 'Android TV', 'ALP' => 'Alpine Linux', 'AMZ' => 'Amazon Linux', 'AMG' => 'AmigaOS', 'ARM' => 'Armadillo OS', 'ATV' => 'tvOS', 'ARL' => 'Arch Linux', 'AOS' => 'AOSC OS', 'ASP' => 'ASPLinux', 'BTR' => 'BackTrack', 'SBA' => 'Bada', 'BEO' => 'BeOS', 'BLB' => 'BlackBerry OS', 'QNX' => 'BlackBerry Tablet OS', 'BOS' => 'Bliss OS', 'BMP' => 'Brew', 'BSN' => 'BrightSignOS', 'CAI' => 'Caixa Mágica', 'CES' => 'CentOS', 'CST' => 'CentOS Stream', 'CLO' => 'Clear Linux OS', 'CLR' => 'ClearOS Mobile', 'COS' => 'Chrome OS', 'CRS' => 'Chromium OS', 'CHN' => 'China OS', 'CYN' => 'CyanogenMod', 'DEB' => 'Debian', 'DEE' => 'Deepin', 'DFB' => 'DragonFly', 'DVK' => 'DVKBuntu', 'ELE' => 'ElectroBSD', 'EUL' => 'EulerOS', 'FED' => 'Fedora', 'FEN' => 'Fenix', 'FOS' => 'Firefox OS', 'FIR' => 'Fire OS', 'FOR' => 'Foresight Linux', 'FRE' => 'Freebox', 'BSD' => 'FreeBSD', 'FRI' => 'FRITZ!OS', 'FYD' => 'FydeOS', 'FUC' => 'Fuchsia', 'GNT' => 'Gentoo', 'GRI' => 'GridOS', 'GTV' => 'Google TV', 'HPX' => 'HP-UX', 'HAI' => 'Haiku OS', 'IPA' => 'iPadOS', 'HAR' => 'HarmonyOS', 'HAS' => 'HasCodingOS', 'HEL' => 'HELIX OS', 'IRI' => 'IRIX', 'INF' => 'Inferno', 'JME' => 'Java ME', 'KOS' => 'KaiOS', 'KAL' => 'Kali', 'KAN' => 'Kanotix', 'KNO' => 'Knoppix', 'KTV' => 'KreaTV', 'KBT' => 'Kubuntu', 'LIN' => 'GNU/Linux', 'LND' => 'LindowsOS', 'LNS' => 'Linspire', 'LEN' => 'Lineage OS', 'LIR' => 'Liri OS', 'LOO' => 'Loongnix', 'LBT' => 'Lubuntu', 'LOS' => 'Lumin OS', 'LUN' => 'LuneOS', 'VLN' => 'VectorLinux', 'MAC' => 'Mac', 'MAE' => 'Maemo', 'MAG' => 'Mageia', 'MDR' => 'Mandriva', 'SMG' => 'MeeGo', 'MCD' => 'MocorDroid', 'MON' => 'moonOS', 'MIN' => 'Mint', 'MLD' => 'MildWild', 'MOR' => 'MorphOS', 'NBS' => 'NetBSD', 'MTK' => 'MTK / Nucleus', 'MRE' => 'MRE', 'WII' => 'Nintendo', 'NDS' => 'Nintendo Mobile', 'NOV' => 'Nova', 'OS2' => 'OS/2', 'T64' => 'OSF1', 'OBS' => 'OpenBSD', 'OVZ' => 'OpenVZ', 'OWR' => 'OpenWrt', 'OTV' => 'Opera TV', 'ORA' => 'Oracle Linux', 'ORD' => 'Ordissimo', 'PAR' => 'Pardus', 'PCL' => 'PCLinuxOS', 'PIC' => 'PICO OS', 'PLA' => 'Plasma Mobile', 'PSP' => 'PlayStation Portable', 'PS3' => 'PlayStation', 'PVE' => 'Proxmox VE', 'PUR' => 'PureOS', 'PIO' => 'Raspberry Pi OS', 'RAS' => 'Raspbian', 'RHT' => 'Red Hat', 'RST' => 'Red Star', 'RED' => 'RedOS', 'REV' => 'Revenge OS', 'ROS' => 'RISC OS', 'ROC' => 'Rocky Linux', 'ROK' => 'Roku OS', 'RSO' => 'Rosa', 'ROU' => 'RouterOS', 'REM' => 'Remix OS', 'RRS' => 'Resurrection Remix OS', 'REX' => 'REX', 'RZD' => 'RazoDroiD', 'SAB' => 'Sabayon', 'SSE' => 'SUSE', 'SAF' => 'Sailfish OS', 'SCI' => 'Scientific Linux', 'SEE' => 'SeewoOS', 'SER' => 'SerenityOS', 'SIR' => 'Sirin OS', 'SLW' => 'Slackware', 'SOS' => 'Solaris', 'SYL' => 'Syllable', 'SYM' => 'Symbian', 'SYS' => 'Symbian OS', 'S40' => 'Symbian OS Series 40', 'S60' => 'Symbian OS Series 60', 'SY3' => 'Symbian^3', 'TEN' => 'TencentOS', 'TDX' => 'ThreadX', 'TIZ' => 'Tizen', 'TIV' => 'TiVo OS', 'TOS' => 'TmaxOS', 'UBT' => 'Ubuntu', 'UOS' => 'UOS', 'VID' => 'VIDAA', 'WAS' => 'watchOS', 'WER' => 'Wear OS', 'WTV' => 'WebTV', 'WHS' => 'Whale OS', 'WIN' => 'Windows', 'WCE' => 'Windows CE', 'WIO' => 'Windows IoT', 'WMO' => 'Windows Mobile', 'WPH' => 'Windows Phone', 'WRT' => 'Windows RT', 'XBX' => 'Xbox', 'XBT' => 'Xubuntu', 'YNS' => 'YunOS', 'ZEN' => 'Zenwalk', 'ZOR' => 'ZorinOS', 'IOS' => 'iOS', 'POS' => 'palmOS', 'WEB' => 'Webian', 'WOS' => 'webOS'];
39 /**
40 * Operating system families mapped to the short codes of the associated operating systems
41 *
42 * @var array
43 */
44 protected static $osFamilies = ['Android' => ['AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR', 'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS', 'WER', 'PIC', 'ARM', 'HEL'], 'AmigaOS' => ['AMG', 'MOR'], 'BlackBerry' => ['BLB', 'QNX'], 'Brew' => ['BMP'], 'BeOS' => ['BEO', 'HAI'], 'Chrome OS' => ['COS', 'CRS', 'FYD', 'SEE'], 'Firefox OS' => ['FOS', 'KOS'], 'Gaming Console' => ['WII', 'PS3'], 'Google TV' => ['GTV'], 'IBM' => ['OS2'], 'iOS' => ['IOS', 'ATV', 'WAS', 'IPA'], 'RISC OS' => ['ROS'], 'GNU/Linux' => ['LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF', 'ORD', 'TOS', 'RSO', 'DEE', 'FRE', 'MAG', 'FEN', 'CAI', 'PCL', 'HAS', 'LOS', 'DVK', 'ROK', 'OWR', 'OTV', 'KTV', 'PUR', 'PLA', 'FUC', 'PAR', 'FOR', 'MON', 'KAN', 'ZEN', 'LND', 'LNS', 'CHN', 'AMZ', 'TEN', 'CST', 'NOV', 'ROU', 'ZOR', 'RED', 'KAL', 'ORA', 'VID', 'TIV', 'BSN', 'RAS', 'UOS', 'PIO', 'FRI', 'LIR', 'WEB', 'SER', 'ASP', 'AOS', 'LOO', 'EUL', 'SCI', 'ALP', 'CLO', 'ROC', 'OVZ', 'PVE', 'RST'], 'Mac' => ['MAC'], 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'], 'Real-time OS' => ['MTK', 'TDX', 'MRE', 'JME', 'REX'], 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE', 'LUN'], 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'], 'Unix' => ['SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF', 'ELE'], 'WebTV' => ['WTV'], 'Windows' => ['WIN'], 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT', 'WIO'], 'Other Smart TV' => ['WHS']];
45 /**
46 * Contains a list of mappings from OS names we use to known client hint values
47 *
48 * @var array<string, array<string>>
49 */
50 protected static $clientHintMapping = ['GNU/Linux' => ['Linux'], 'Mac' => ['MacOS']];
51 /**
52 * Operating system families that are known as desktop only
53 *
54 * @var array
55 */
56 protected static $desktopOsArray = ['AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS', 'Chromium OS'];
57 /**
58 * Fire OS version mapping
59 *
60 * @var array
61 */
62 private $fireOsVersionMapping = ['11' => '8', '10' => '8', '9' => '7', '7' => '6', '5' => '5', '4.4.3' => '4.5.1', '4.4.2' => '4', '4.2.2' => '3', '4.0.3' => '3', '4.0.2' => '3', '4' => '2', '2' => '1'];
63 /**
64 * Lineage OS version mapping
65 *
66 * @var array
67 */
68 private $lineageOsVersionMapping = ['14' => '21', '13' => '20.0', '12.1' => '19.1', '12' => '19.0', '11' => '18.0', '10' => '17.0', '9' => '16.0', '8.1.0' => '15.1', '8.0.0' => '15.0', '7.1.2' => '14.1', '7.1.1' => '14.1', '7.0' => '14.0', '6.0.1' => '13.0', '6.0' => '13.0', '5.1.1' => '12.1', '5.0.2' => '12.0', '5.0' => '12.0', '4.4.4' => '11.0', '4.3' => '10.2', '4.2.2' => '10.1', '4.0.4' => '9.1.0'];
69 /**
70 * Returns all available operating systems
71 *
72 * @return array
73 */
74 public static function getAvailableOperatingSystems() : array
75 {
76 return self::$operatingSystems;
77 }
78 /**
79 * Returns all available operating system families
80 *
81 * @return array
82 */
83 public static function getAvailableOperatingSystemFamilies() : array
84 {
85 return self::$osFamilies;
86 }
87 /**
88 * Returns the os name and shot name
89 *
90 * @param string $name
91 *
92 * @return array
93 */
94 public static function getShortOsData(string $name) : array
95 {
96 $short = 'UNK';
97 foreach (self::$operatingSystems as $osShort => $osName) {
98 if (\strtolower($name) !== \strtolower($osName)) {
99 continue;
100 }
101 $name = $osName;
102 $short = $osShort;
103 break;
104 }
105 return \compact('short', 'name');
106 }
107 /**
108 * @inheritdoc
109 */
110 public function parse() : ?array
111 {
112 $osFromClientHints = $this->parseOsFromClientHints();
113 $osFromUserAgent = $this->parseOsFromUserAgent();
114 if (!empty($osFromClientHints['name'])) {
115 $name = $osFromClientHints['name'];
116 $version = $osFromClientHints['version'];
117 // use version from user agent if non was provided in client hints, but os family from useragent matches
118 if (empty($version) && self::getOsFamily($name) === self::getOsFamily($osFromUserAgent['name'])) {
119 $version = $osFromUserAgent['version'];
120 }
121 // If the OS name detected from client hints matches the OS family from user agent
122 // but the os name is another, we use the one from user agent, as it might be more detailed
123 if (self::getOsFamily($osFromUserAgent['name']) === $name && $osFromUserAgent['name'] !== $name) {
124 $name = $osFromUserAgent['name'];
125 if ('HarmonyOS' === $name) {
126 $version = '';
127 }
128 if ('PICO OS' === $name) {
129 $version = $osFromUserAgent['version'];
130 }
131 if ('Fire OS' === $osFromUserAgent['name']) {
132 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
133 $version = $this->fireOsVersionMapping[$version] ?? $this->fireOsVersionMapping[$majorVersion] ?? '';
134 }
135 }
136 $short = $osFromClientHints['short_name'];
137 // Chrome OS is in some cases reported as Linux in client hints, we fix this only if the version matches
138 if ('GNU/Linux' === $name && 'Chrome OS' === $osFromUserAgent['name'] && $osFromClientHints['version'] === $osFromUserAgent['version']) {
139 $name = $osFromUserAgent['name'];
140 $short = $osFromUserAgent['short_name'];
141 }
142 } elseif (!empty($osFromUserAgent['name'])) {
143 $name = $osFromUserAgent['name'];
144 $version = $osFromUserAgent['version'];
145 $short = $osFromUserAgent['short_name'];
146 } else {
147 return [];
148 }
149 $platform = $this->parsePlatform();
150 $family = self::getOsFamily($short);
151 $androidApps = ['com.hisense.odinbrowser', 'com.seraphic.openinet.pre', 'com.appssppa.idesktoppcbrowser', 'every.browser.inc'];
152 if (null !== $this->clientHints) {
153 if (\in_array($this->clientHints->getApp(), $androidApps) && 'Android' !== $name) {
154 $name = 'Android';
155 $family = 'Android';
156 $short = 'ADR';
157 $version = '';
158 }
159 if ('org.lineageos.jelly' === $this->clientHints->getApp() && 'Lineage OS' !== $name) {
160 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
161 $name = 'Lineage OS';
162 $family = 'Android';
163 $short = 'LEN';
164 $version = $this->lineageOsVersionMapping[$version] ?? $this->lineageOsVersionMapping[$majorVersion] ?? '';
165 }
166 if ('org.mozilla.tv.firefox' === $this->clientHints->getApp() && 'Fire OS' !== $name) {
167 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
168 $name = 'Fire OS';
169 $family = 'Android';
170 $short = 'FIR';
171 $version = $this->fireOsVersionMapping[$version] ?? $this->fireOsVersionMapping[$majorVersion] ?? '';
172 }
173 }
174 $return = ['name' => $name, 'short_name' => $short, 'version' => $version, 'platform' => $platform, 'family' => $family];
175 if (\in_array($return['name'], self::$operatingSystems)) {
176 $return['short_name'] = \array_search($return['name'], self::$operatingSystems);
177 }
178 return $return;
179 }
180 /**
181 * Returns the operating system family for the given operating system
182 *
183 * @param string $osLabel name or short name
184 *
185 * @return string|null If null, "Unknown"
186 */
187 public static function getOsFamily(string $osLabel) : ?string
188 {
189 if (\in_array($osLabel, self::$operatingSystems)) {
190 $osLabel = \array_search($osLabel, self::$operatingSystems);
191 }
192 foreach (self::$osFamilies as $family => $labels) {
193 if (\in_array($osLabel, $labels)) {
194 return (string) $family;
195 }
196 }
197 return null;
198 }
199 /**
200 * Returns true if OS is desktop
201 *
202 * @param string $osName OS short name
203 *
204 * @return bool
205 */
206 public static function isDesktopOs(string $osName) : bool
207 {
208 $osFamily = self::getOsFamily($osName);
209 return \in_array($osFamily, self::$desktopOsArray);
210 }
211 /**
212 * Returns the full name for the given short name
213 *
214 * @param string $os
215 * @param string|null $ver
216 *
217 * @return ?string
218 */
219 public static function getNameFromId(string $os, ?string $ver = null) : ?string
220 {
221 if (\array_key_exists($os, self::$operatingSystems)) {
222 $osFullName = self::$operatingSystems[$os];
223 return \trim($osFullName . ' ' . $ver);
224 }
225 return null;
226 }
227 /**
228 * Returns the OS that can be safely detected from client hints
229 *
230 * @return array
231 */
232 protected function parseOsFromClientHints() : array
233 {
234 $name = $version = $short = '';
235 if ($this->clientHints instanceof ClientHints && $this->clientHints->getOperatingSystem()) {
236 $hintName = $this->applyClientHintMapping($this->clientHints->getOperatingSystem());
237 foreach (self::$operatingSystems as $osShort => $osName) {
238 if ($this->fuzzyCompare($hintName, $osName)) {
239 $name = $osName;
240 $short = $osShort;
241 break;
242 }
243 }
244 $version = $this->clientHints->getOperatingSystemVersion();
245 if ('Windows' === $name) {
246 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
247 if ($majorVersion > 0 && $majorVersion < 11) {
248 $version = '10';
249 } elseif ($majorVersion > 10) {
250 $version = '11';
251 }
252 }
253 if (0 === (int) $version) {
254 $version = '';
255 }
256 }
257 return ['name' => $name, 'short_name' => $short, 'version' => $this->buildVersion($version, [])];
258 }
259 /**
260 * Returns the OS that can be detected from useragent
261 *
262 * @return array
263 *
264 * @throws \Exception
265 */
266 protected function parseOsFromUserAgent() : array
267 {
268 $osRegex = $matches = [];
269 $name = $version = $short = '';
270 foreach ($this->getRegexes() as $osRegex) {
271 $matches = $this->matchUserAgent($osRegex['regex']);
272 if ($matches) {
273 break;
274 }
275 }
276 if (!empty($matches)) {
277 $name = $this->buildByMatch($osRegex['name'], $matches);
278 ['name' => $name, 'short' => $short] = self::getShortOsData($name);
279 $version = \array_key_exists('version', $osRegex) ? $this->buildVersion((string) $osRegex['version'], $matches) : '';
280 foreach ($osRegex['versions'] ?? [] as $regex) {
281 $matches = $this->matchUserAgent($regex['regex']);
282 if (!$matches) {
283 continue;
284 }
285 if (\array_key_exists('name', $regex)) {
286 $name = $this->buildByMatch($regex['name'], $matches);
287 ['name' => $name, 'short' => $short] = self::getShortOsData($name);
288 }
289 if (\array_key_exists('version', $regex)) {
290 $version = $this->buildVersion((string) $regex['version'], $matches);
291 }
292 break;
293 }
294 }
295 return ['name' => $name, 'short_name' => $short, 'version' => $version];
296 }
297 /**
298 * Parse current UserAgent string for the operating system platform
299 *
300 * @return string
301 */
302 protected function parsePlatform() : string
303 {
304 // Use architecture from client hints if available
305 if ($this->clientHints instanceof ClientHints && $this->clientHints->getArchitecture()) {
306 $arch = \strtolower($this->clientHints->getArchitecture());
307 if (\false !== \strpos($arch, 'arm')) {
308 return 'ARM';
309 }
310 if (\false !== \strpos($arch, 'loongarch64')) {
311 return 'LoongArch64';
312 }
313 if (\false !== \strpos($arch, 'mips')) {
314 return 'MIPS';
315 }
316 if (\false !== \strpos($arch, 'sh4')) {
317 return 'SuperH';
318 }
319 if (\false !== \strpos($arch, 'sparc64')) {
320 return 'SPARC64';
321 }
322 if (\false !== \strpos($arch, 'x64') || \false !== \strpos($arch, 'x86') && '64' === $this->clientHints->getBitness()) {
323 return 'x64';
324 }
325 if (\false !== \strpos($arch, 'x86')) {
326 return 'x86';
327 }
328 }
329 if ($this->matchUserAgent('arm|.*arm64|aarch64|Apple ?TV|Watch ?OS|Watch1,[12]')) {
330 return 'ARM';
331 }
332 if ($this->matchUserAgent('loongarch64')) {
333 return 'LoongArch64';
334 }
335 if ($this->matchUserAgent('mips')) {
336 return 'MIPS';
337 }
338 if ($this->matchUserAgent('sh4')) {
339 return 'SuperH';
340 }
341 if ($this->matchUserAgent('sparc64')) {
342 return 'SPARC64';
343 }
344 if ($this->matchUserAgent('64-?bit|WOW64|(?:Intel)?x64|WINDOWS_64|win64|amd64|x86_?64')) {
345 return 'x64';
346 }
347 if ($this->matchUserAgent('.*32bit|.*win32|(?:i[0-9]|x)86|i86pc')) {
348 return 'x86';
349 }
350 return '';
351 }
352 }
353