PluginProbe
Independent Analytics – WordPress Analytics Plugin / 1.29.0
Independent Analytics – WordPress Analytics Plugin v1.29.0
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 All 120 releases
independent-analytics / vendor / matomo / device-detector / Parser / OperatingSystem.php
OperatingSystem.php
307 lines 14.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 IAWP_SCOPED\DeviceDetector\Parser;
12
13 use IAWP_SCOPED\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 */
22 class OperatingSystem extends AbstractParser
23 {
24 /**
25 * @var string
26 */
27 protected $fixtureFile = 'regexes/oss.yml';
28 /**
29 * @var string
30 */
31 protected $parserName = 'os';
32 /**
33 * Known operating systems mapped to their internal short codes
34 *
35 * @var array
36 */
37 protected static $operatingSystems = ['AIX' => 'AIX', 'AND' => 'Android', 'ADR' => 'Android TV', 'AMZ' => 'Amazon Linux', 'AMG' => 'AmigaOS', 'ATV' => 'tvOS', 'ARL' => 'Arch Linux', 'BTR' => 'BackTrack', 'SBA' => 'Bada', 'BEO' => 'BeOS', 'BLB' => 'BlackBerry OS', 'QNX' => 'BlackBerry Tablet OS', 'BOS' => 'Bliss OS', 'BMP' => 'Brew', 'CAI' => 'Caixa Mágica', 'CES' => 'CentOS', 'CST' => 'CentOS Stream', 'CLR' => 'ClearOS Mobile', 'COS' => 'Chrome OS', 'CRS' => 'Chromium OS', 'CHN' => 'China OS', 'CYN' => 'CyanogenMod', 'DEB' => 'Debian', 'DEE' => 'Deepin', 'DFB' => 'DragonFly', 'DVK' => 'DVKBuntu', 'FED' => 'Fedora', 'FEN' => 'Fenix', 'FOS' => 'Firefox OS', 'FIR' => 'Fire OS', 'FOR' => 'Foresight Linux', 'FRE' => 'Freebox', 'BSD' => 'FreeBSD', 'FYD' => 'FydeOS', 'FUC' => 'Fuchsia', 'GNT' => 'Gentoo', 'GRI' => 'GridOS', 'GTV' => 'Google TV', 'HPX' => 'HP-UX', 'HAI' => 'Haiku OS', 'IPA' => 'iPadOS', 'HAR' => 'HarmonyOS', 'HAS' => 'HasCodingOS', 'IRI' => 'IRIX', 'INF' => 'Inferno', 'JME' => 'Java ME', 'KOS' => 'KaiOS', 'KAN' => 'Kanotix', 'KNO' => 'Knoppix', 'KTV' => 'KreaTV', 'KBT' => 'Kubuntu', 'LIN' => 'GNU/Linux', 'LND' => 'LindowsOS', 'LNS' => 'Linspire', 'LEN' => 'Lineage OS', 'LBT' => 'Lubuntu', 'LOS' => 'Lumin OS', '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', 'OWR' => 'OpenWrt', 'OTV' => 'Opera TV', 'ORD' => 'Ordissimo', 'PAR' => 'Pardus', 'PCL' => 'PCLinuxOS', 'PLA' => 'Plasma Mobile', 'PSP' => 'PlayStation Portable', 'PS3' => 'PlayStation', 'PUR' => 'PureOS', 'RHT' => 'Red Hat', 'RED' => 'RedOS', 'REV' => 'Revenge OS', 'ROS' => 'RISC OS', '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', 'SEE' => 'SeewoOS', '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', 'TOS' => 'TmaxOS', 'UBT' => 'Ubuntu', 'WAS' => 'watchOS', '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', 'WOS' => 'webOS'];
38 /**
39 * Operating system families mapped to the short codes of the associated operating systems
40 *
41 * @var array
42 */
43 protected static $osFamilies = ['Android' => ['AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR', 'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS'], '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'], 'Mac' => ['MAC'], 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'], 'Real-time OS' => ['MTK', 'TDX', 'MRE', 'JME', 'REX'], 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE'], 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'], 'Unix' => ['SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF'], 'WebTV' => ['WTV'], 'Windows' => ['WIN'], 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT', 'WIO'], 'Other Smart TV' => ['WHS']];
44 /**
45 * Contains a list of mappings from OS names we use to known client hint values
46 *
47 * @var array<string, array<string>>
48 */
49 protected static $clientHintMapping = ['GNU/Linux' => ['Linux'], 'Mac' => ['MacOS']];
50 /**
51 * Operating system families that are known as desktop only
52 *
53 * @var array
54 */
55 protected static $desktopOsArray = ['AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS', 'Chromium OS'];
56 /**
57 * Returns all available operating systems
58 *
59 * @return array
60 */
61 public static function getAvailableOperatingSystems() : array
62 {
63 return self::$operatingSystems;
64 }
65 /**
66 * Returns all available operating system families
67 *
68 * @return array
69 */
70 public static function getAvailableOperatingSystemFamilies() : array
71 {
72 return self::$osFamilies;
73 }
74 /**
75 * Returns the os name and shot name
76 *
77 * @param string $name
78 *
79 * @return array
80 */
81 public static function getShortOsData(string $name) : array
82 {
83 $short = 'UNK';
84 foreach (self::$operatingSystems as $osShort => $osName) {
85 if (\strtolower($name) !== \strtolower($osName)) {
86 continue;
87 }
88 $name = $osName;
89 $short = $osShort;
90 break;
91 }
92 return \compact('short', 'name');
93 }
94 /**
95 * @inheritdoc
96 */
97 public function parse() : ?array
98 {
99 $osFromClientHints = $this->parseOsFromClientHints();
100 $osFromUserAgent = $this->parseOsFromUserAgent();
101 if (!empty($osFromClientHints['name'])) {
102 $name = $osFromClientHints['name'];
103 $version = $osFromClientHints['version'];
104 // use version from user agent if non was provided in client hints, but os family from useragent matches
105 if (empty($version) && self::getOsFamily($name) === self::getOsFamily($osFromUserAgent['name'])) {
106 $version = $osFromUserAgent['version'];
107 }
108 // If the OS name detected from client hints matches the OS family from user agent
109 // but the os name is another, we use the one from user agent, as it might be more detailed
110 if (self::getOsFamily($osFromUserAgent['name']) === $name && $osFromUserAgent['name'] !== $name) {
111 $name = $osFromUserAgent['name'];
112 if ('HarmonyOS' === $name) {
113 $version = '';
114 }
115 }
116 $short = $osFromClientHints['short_name'];
117 // Chrome OS is in some cases reported as Linux in client hints, we fix this only if the version matches
118 if ('GNU/Linux' === $name && 'Chrome OS' === $osFromUserAgent['name'] && $osFromClientHints['version'] === $osFromUserAgent['version']) {
119 $name = $osFromUserAgent['name'];
120 $short = $osFromUserAgent['short_name'];
121 }
122 } elseif (!empty($osFromUserAgent['name'])) {
123 $name = $osFromUserAgent['name'];
124 $version = $osFromUserAgent['version'];
125 $short = $osFromUserAgent['short_name'];
126 } else {
127 return [];
128 }
129 $platform = $this->parsePlatform();
130 $family = self::getOsFamily($short);
131 $androidApps = ['com.hisense.odinbrowser', 'com.seraphic.openinet.pre', 'com.appssppa.idesktoppcbrowser'];
132 if (null !== $this->clientHints) {
133 if (\in_array($this->clientHints->getApp(), $androidApps) && 'Android' !== $name) {
134 $name = 'Android';
135 $family = 'Android';
136 $short = 'ADR';
137 $version = '';
138 }
139 }
140 $return = ['name' => $name, 'short_name' => $short, 'version' => $version, 'platform' => $platform, 'family' => $family];
141 if (\in_array($return['name'], self::$operatingSystems)) {
142 $return['short_name'] = \array_search($return['name'], self::$operatingSystems);
143 }
144 return $return;
145 }
146 /**
147 * Returns the operating system family for the given operating system
148 *
149 * @param string $osLabel name or short name
150 *
151 * @return string|null If null, "Unknown"
152 */
153 public static function getOsFamily(string $osLabel) : ?string
154 {
155 if (\in_array($osLabel, self::$operatingSystems)) {
156 $osLabel = \array_search($osLabel, self::$operatingSystems);
157 }
158 foreach (self::$osFamilies as $family => $labels) {
159 if (\in_array($osLabel, $labels)) {
160 return (string) $family;
161 }
162 }
163 return null;
164 }
165 /**
166 * Returns true if OS is desktop
167 *
168 * @param string $osName OS short name
169 *
170 * @return bool
171 */
172 public static function isDesktopOs(string $osName) : bool
173 {
174 $osFamily = self::getOsFamily($osName);
175 return \in_array($osFamily, self::$desktopOsArray);
176 }
177 /**
178 * Returns the full name for the given short name
179 *
180 * @param string $os
181 * @param string|null $ver
182 *
183 * @return ?string
184 */
185 public static function getNameFromId(string $os, ?string $ver = null) : ?string
186 {
187 if (\array_key_exists($os, self::$operatingSystems)) {
188 $osFullName = self::$operatingSystems[$os];
189 return \trim($osFullName . ' ' . $ver);
190 }
191 return null;
192 }
193 /**
194 * Returns the OS that can be safely detected from client hints
195 *
196 * @return array
197 */
198 protected function parseOsFromClientHints() : array
199 {
200 $name = $version = $short = '';
201 if ($this->clientHints instanceof ClientHints && $this->clientHints->getOperatingSystem()) {
202 $hintName = $this->applyClientHintMapping($this->clientHints->getOperatingSystem());
203 foreach (self::$operatingSystems as $osShort => $osName) {
204 if ($this->fuzzyCompare($hintName, $osName)) {
205 $name = $osName;
206 $short = $osShort;
207 break;
208 }
209 }
210 $version = $this->clientHints->getOperatingSystemVersion();
211 if ('Windows' === $name) {
212 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
213 if ($majorVersion > 0 && $majorVersion < 11) {
214 $version = '10';
215 } elseif ($majorVersion > 10) {
216 $version = '11';
217 }
218 }
219 if (0 === (int) $version) {
220 $version = '';
221 }
222 }
223 return ['name' => $name, 'short_name' => $short, 'version' => $this->buildVersion($version, [])];
224 }
225 /**
226 * Returns the OS that can be detected from useragent
227 *
228 * @return array
229 *
230 * @throws \Exception
231 */
232 protected function parseOsFromUserAgent() : array
233 {
234 $osRegex = $matches = [];
235 $name = $version = $short = '';
236 foreach ($this->getRegexes() as $osRegex) {
237 $matches = $this->matchUserAgent($osRegex['regex']);
238 if ($matches) {
239 break;
240 }
241 }
242 if (!empty($matches)) {
243 $name = $this->buildByMatch($osRegex['name'], $matches);
244 ['name' => $name, 'short' => $short] = self::getShortOsData($name);
245 $version = \array_key_exists('version', $osRegex) ? $this->buildVersion((string) $osRegex['version'], $matches) : '';
246 foreach ($osRegex['versions'] ?? [] as $regex) {
247 $matches = $this->matchUserAgent($regex['regex']);
248 if (!$matches) {
249 continue;
250 }
251 if (\array_key_exists('name', $regex)) {
252 $name = $this->buildByMatch($regex['name'], $matches);
253 ['name' => $name, 'short' => $short] = self::getShortOsData($name);
254 }
255 if (\array_key_exists('version', $regex)) {
256 $version = $this->buildVersion((string) $regex['version'], $matches);
257 }
258 break;
259 }
260 }
261 return ['name' => $name, 'short_name' => $short, 'version' => $version];
262 }
263 /**
264 * Parse current UserAgent string for the operating system platform
265 *
266 * @return string
267 */
268 protected function parsePlatform() : string
269 {
270 // Use architecture from client hints if available
271 if ($this->clientHints instanceof ClientHints && $this->clientHints->getArchitecture()) {
272 $arch = \strtolower($this->clientHints->getArchitecture());
273 if (\false !== \strpos($arch, 'arm')) {
274 return 'ARM';
275 }
276 if (\false !== \strpos($arch, 'mips')) {
277 return 'MIPS';
278 }
279 if (\false !== \strpos($arch, 'sh4')) {
280 return 'SuperH';
281 }
282 if (\false !== \strpos($arch, 'x64') || \false !== \strpos($arch, 'x86') && '64' === $this->clientHints->getBitness()) {
283 return 'x64';
284 }
285 if (\false !== \strpos($arch, 'x86')) {
286 return 'x86';
287 }
288 }
289 if ($this->matchUserAgent('arm|aarch64|Apple ?TV|Watch ?OS|Watch1,[12]')) {
290 return 'ARM';
291 }
292 if ($this->matchUserAgent('mips')) {
293 return 'MIPS';
294 }
295 if ($this->matchUserAgent('sh4')) {
296 return 'SuperH';
297 }
298 if ($this->matchUserAgent('64-?bit|WOW64|(?:Intel)?x64|WINDOWS_64|win64|amd64|x86_?64')) {
299 return 'x64';
300 }
301 if ($this->matchUserAgent('.+32bit|.+win32|(?:i[0-9]|x)86|i86pc')) {
302 return 'x86';
303 }
304 return '';
305 }
306 }
307