PluginProbe ʕ •ᴥ•ʔ
Kubio AI Page Builder / 2.8.4
Kubio AI Page Builder v2.8.4
2.8.4 2.8.3 2.8.2 2.8.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 2.0.0 2.1.1 2.1.2 2.1.3 2.2.0 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.5 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.6.3 2.6.5 2.6.6 2.6.7 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0
kubio / vendor / wolfcast / browser-detection / lib / BrowserDetection.php
kubio / vendor / wolfcast / browser-detection / lib Last commit date
BrowserDetection.php 2 years ago
BrowserDetection.php
2383 lines
1 <?php
2
3 /**
4 * Browser detection class file.
5 * This file contains everything required to use the BrowserDetection class. Tested with PHP 5.3.29 - 7.4.0.
6 *
7 * This program is free software; you can redistribute it and/or modify it under the terms of EITHER the MIT License
8 * (SPDX short identifier: MIT) OR the GNU Lesser General Public License Version 3 (SPDX short identifier:
9 * LGPL-3.0-only). License details can be found in the LICENSE-*.md files of this project or on the below URLs.
10 *
11 * @package Browser_Detection
12 * @version 2.9.7
13 * @last-modified September 20, 2022
14 * @author Alexandre Valiquette
15 * @copyright Copyright (c) 2022, Wolfcast
16 * @license https://spdx.org/licenses/GPL-3.0-only.html
17 * @license https://spdx.org/licenses/MIT.html
18 * @link https://wolfcast.com/
19 */
20
21
22 namespace Wolfcast;
23
24
25 /**
26 * The BrowserDetection class facilitates the identification of the user's environment such as Web browser, version,
27 * platform and device type.
28 *
29 * Typical usage:
30 *
31 * $browser = new Wolfcast\BrowserDetection();
32 * if ($browser->getName() == Wolfcast\BrowserDetection::BROWSER_FIREFOX &&
33 * $browser->compareVersions($browser->getVersion(), '5.0') >= 0) {
34 * echo 'You are using FireFox version 5 or greater.';
35 * }
36 *
37 * The class is a rewrite of Chris Schuld's Browser class version 1.9 which is mostly unmaintained since August 20th,
38 * 2010. Chris' class was based on the original work from Gary White.
39 *
40 * Updates:
41 *
42 * 2022-09-20: Version 2.9.7
43 * + Changed licensing to dual licensing: MIT or LGPL-3.0-only. This has no impact on existing users. You can continue
44 * under the previous license or switch to MIT.
45 *
46 * 2022-05-01: Version 2.9.6
47 * + Added support for Chrome OS.
48 * + Added support for macOS Monterey and macOS Big Sur.
49 * + Now correctly detects AArch64 as 64-bit.
50 * + Added support for PHP 8.
51 * + Tested with latest Web Browsers and platforms.
52 *
53 * 2020-02-02: Version 2.9.5
54 * + WARNING! Breaking change: complete rework of robots detection. Now robot name and version is detected in addition
55 * of browser name and version. Use getRobotName() and getRobotVersion() when isRobot() is true.
56 * + WARNING! Breaking change: due to robots detection rework the following methods signatures has changed (isRobot
57 * parameter removed): addCustomBrowserDetection(), checkSimpleBrowserUA(), checkBrowserUAWithVersion().
58 * + Added possibility to support new robots with addCustomRobotDetection().
59 * + Added support for the new Microsoft Edge based on Chromium.
60 * + Added version names for Android 10 and later (Google no longer use candy names for new versions).
61 * + Added macOS Catalina detection.
62 * + Added Windows Server 2019 detection (Windows Server 2016 can be no longer detected due to the fact that they both
63 * use the same version number and that the build is not included in the user agent).
64 *
65 * 2019-03-27: Version 2.9.3
66 * + Fixed Edge detection on Android.
67 * + Added Android Q detection.
68 * + Now filtering superglobals.
69 *
70 * 2019-02-28: Version 2.9.2
71 * + Fixed Opera detection.
72 *
73 * 2018-08-23: Version 2.9.1
74 * + Fixed Chrome detection under iOS.
75 * + Added Android Pie detection.
76 * + Added macOS Mojave detection.
77 *
78 * 2018-07-15: Version 2.9.0
79 * + WARNING! Breaking change: new Wolfcast namespace. Use new Wolfcast\BrowserDetection().
80 * + iPad, iPhone and iPod are all under iOS now.
81 * + Added Android Oreo detection.
82 * + Added macOS High Sierra detection.
83 * + Added UC Browser detection.
84 * + Improved regular expressions (even less false positives).
85 * + Removed AOL detection.
86 * + Removed the following Web browsers detection: Amaya, Galeon, NetPositive, OmniWeb, Vivaldi detection (use
87 * addCustomBrowserDetection()).
88 * + Removed the following legacy platforms detection: BeOS, OS/2, SunOS (use addCustomPlatformDetection()).
89 *
90 * 2016-11-28: Version 2.5.1
91 * + Better detection of 64-bit platforms.
92 *
93 * 2016-08-19: Version 2.5.0
94 * + Platform version and platform version name are now supported for Mac.
95 * + Fixed platform version name for Android.
96 *
97 * 2016-08-02: Version 2.4.0
98 * + Platform version and platform version name are now supported for Android.
99 * + Added support for the Samsung Internet browser.
100 * + Added support for the Vivaldi browser.
101 * + Better support for legacy Windows versions.
102 *
103 * 2016-02-11: Version 2.3.0
104 * + WARNING! Breaking change: public method getBrowser() is renamed to getName().
105 * + WARNING! Breaking change: changed the compareVersions() return values to be more in line with other libraries.
106 * + You can now get the exact platform version (name or version numbers) on which the browser is run on with
107 * getPlatformVersion(). Only working with Windows operating systems at the moment.
108 * + You can now determine if the browser is executed from a 64-bit platform with is64bitPlatform().
109 * + Better detection of mobile platform for Googlebot.
110 *
111 * 2016-01-04: Version 2.2.0
112 * + Added support for Microsoft Edge.
113 *
114 * 2014-12-30: Version 2.1.2
115 * + Better detection of Opera.
116 *
117 * 2014-07-11: Version 2.1.1
118 * + Better detection of mobile devices and platforms.
119 *
120 * 2014-06-04: Version 2.1.0
121 * + Added support for IE 11+.
122 *
123 * 2013-05-27: Version 2.0.0 which is (almost) a complete rewrite based on Chris Schuld's Browser class version 1.9 plus
124 * changes below.
125 * + Added support for Opera Mobile
126 * + Added support for the Windows Phone (formerly Windows Mobile) platform
127 * + Added support for BlackBerry Tablet OS and BlackBerry 10
128 * + Added support for the Symbian platform
129 * + Added support for Bingbot
130 * + Added support for the Yahoo! Multimedia crawler
131 * + Removed iPhone/iPad/iPod browsers since there are not browsers but platforms - test them with getPlatform()
132 * + Removed support for Shiretoko (Firefox 3.5 alpha/beta) and MSN Browser
133 * + Merged Nokia and Nokia S60
134 * + Updated some deprecated browser names
135 * + Many public methods are now protected
136 * + Documentation updated
137 *
138 * 2010-07-04:
139 * + Added detection of IE compatibility view - test with getIECompatibilityView()
140 * + Added support for all (deprecated) Netscape versions
141 * + Added support for Safari < 3.0
142 * + Better Firefox version parsing
143 * + Better Opera version parsing
144 * + Better Mozilla detection
145 *
146 * @package Browser_Detection
147 * @version 2.9.7
148 * @last-modified September 20, 2022
149 * @author Alexandre Valiquette, Chris Schuld, Gary White
150 * @copyright Copyright (c) 2022, Wolfcast
151 * @license https://spdx.org/licenses/GPL-3.0-only.html
152 * @license https://spdx.org/licenses/MIT.html
153 * @link https://wolfcast.com/
154 * @link https://wolfcast.com/open-source/browser-detection/tutorial.php
155 * @link https://chrisschuld.com/
156 * @link https://www.apptools.com/phptools/browser/
157 */
158 class BrowserDetection
159 {
160
161 /**#@+
162 * Constant for the name of the Web browser.
163 */
164 const BROWSER_ANDROID = 'Android';
165 const BROWSER_BLACKBERRY = 'BlackBerry';
166 const BROWSER_CHROME = 'Chrome';
167 const BROWSER_EDGE = 'Edge';
168 const BROWSER_FIREBIRD = 'Firebird';
169 const BROWSER_FIREFOX = 'Firefox';
170 const BROWSER_ICAB = 'iCab';
171 const BROWSER_ICECAT = 'GNU IceCat';
172 const BROWSER_ICEWEASEL = 'GNU IceWeasel';
173 const BROWSER_IE = 'Internet Explorer';
174 const BROWSER_IE_MOBILE = 'Internet Explorer Mobile';
175 const BROWSER_KONQUEROR = 'Konqueror';
176 const BROWSER_LYNX = 'Lynx';
177 const BROWSER_MOZILLA = 'Mozilla';
178 const BROWSER_MSNTV = 'MSN TV';
179 const BROWSER_NETSCAPE = 'Netscape';
180 const BROWSER_NOKIA = 'Nokia Browser';
181 const BROWSER_OPERA = 'Opera';
182 const BROWSER_OPERA_MINI = 'Opera Mini';
183 const BROWSER_OPERA_MOBILE = 'Opera Mobile';
184 const BROWSER_PHOENIX = 'Phoenix';
185 const BROWSER_SAFARI = 'Safari';
186 const BROWSER_SAMSUNG = 'Samsung Internet';
187 const BROWSER_TABLET_OS = 'BlackBerry Tablet OS';
188 const BROWSER_UC = 'UC Browser';
189 const BROWSER_UNKNOWN = 'unknown';
190 /**#@-*/
191
192 /**#@+
193 * Constant for the name of the platform on which the Web browser runs.
194 */
195 const PLATFORM_ANDROID = 'Android';
196 const PLATFORM_BLACKBERRY = 'BlackBerry';
197 const PLATFORM_CHROME_OS = 'Chrome OS';
198 const PLATFORM_FREEBSD = 'FreeBSD';
199 const PLATFORM_IOS = 'iOS';
200 const PLATFORM_LINUX = 'Linux';
201 const PLATFORM_MACINTOSH = 'Macintosh';
202 const PLATFORM_NETBSD = 'NetBSD';
203 const PLATFORM_NOKIA = 'Nokia';
204 const PLATFORM_OPENBSD = 'OpenBSD';
205 const PLATFORM_OPENSOLARIS = 'OpenSolaris';
206 const PLATFORM_SYMBIAN = 'Symbian';
207 const PLATFORM_UNKNOWN = 'unknown';
208 const PLATFORM_VERSION_UNKNOWN = 'unknown';
209 const PLATFORM_WINDOWS = 'Windows';
210 const PLATFORM_WINDOWS_CE = 'Windows CE';
211 const PLATFORM_WINDOWS_PHONE = 'Windows Phone';
212 /**#@-*/
213
214 /**#@+
215 * Constant for the name of the robot.
216 */
217 const ROBOT_BINGBOT = 'Bingbot';
218 const ROBOT_GOOGLEBOT = 'Googlebot';
219 const ROBOT_MSNBOT = 'MSNBot';
220 const ROBOT_SLURP = 'Yahoo! Slurp';
221 const ROBOT_UNKNOWN = '';
222 const ROBOT_VERSION_UNKNOWN = '';
223 const ROBOT_W3CVALIDATOR = 'W3C Validator';
224 const ROBOT_YAHOO_MM = 'Yahoo! Multimedia';
225 /**#@-*/
226
227 /**
228 * Version unknown constant.
229 */
230 const VERSION_UNKNOWN = 'unknown';
231
232
233 /**
234 * @var string
235 * @access private
236 */
237 private $_agent = '';
238
239 /**
240 * @var string
241 * @access private
242 */
243 private $_browserName = '';
244
245 /**
246 * @var string
247 * @access private
248 */
249 private $_compatibilityViewName = '';
250
251 /**
252 * @var string
253 * @access private
254 */
255 private $_compatibilityViewVer = '';
256
257 /**
258 * @var array
259 * @access private
260 */
261 private $_customBrowserDetection = array();
262
263 /**
264 * @var array
265 * @access private
266 */
267 private $_customPlatformDetection = array();
268
269 /**
270 * @var array
271 * @access private
272 */
273 private $_customRobotDetection = array();
274
275 /**
276 * @var boolean
277 * @access private
278 */
279 private $_is64bit = false;
280
281 /**
282 * @var boolean
283 * @access private
284 */
285 private $_isMobile = false;
286
287 /**
288 * @var boolean
289 * @access private
290 */
291 private $_isRobot = false;
292
293 /**
294 * @var string
295 * @access private
296 */
297 private $_platform = '';
298
299 /**
300 * @var string
301 * @access private
302 */
303 private $_platformVersion = '';
304
305 /**
306 * @var string
307 * @access private
308 */
309 private $_robotName = '';
310
311 /**
312 * @var string
313 * @access private
314 */
315 private $_robotVersion = '';
316
317 /**
318 * @var string
319 * @access private
320 */
321 private $_version = '';
322
323
324 //--- MAGIC METHODS ------------------------------------------------------------------------------------------------
325
326
327 /**
328 * BrowserDetection class constructor.
329 * @param string $useragent (optional) The user agent to work with. Leave empty for the current user agent
330 * (contained in $_SERVER['HTTP_USER_AGENT']).
331 */
332 public function __construct($useragent = '')
333 {
334 $this->setUserAgent($useragent);
335 }
336
337 /**
338 * Determine how the class will react when it is treated like a string.
339 * @return string Returns an HTML formatted string with a summary of the browser informations.
340 */
341 public function __toString()
342 {
343 $result = '';
344
345 $values = array();
346 $values[] = array('label' => 'User agent', 'value' => $this->getUserAgent());
347 $values[] = array('label' => 'Browser name', 'value' => $this->getName());
348 $values[] = array('label' => 'Browser version', 'value' => $this->getVersion());
349 $values[] = array('label' => 'Platform family', 'value' => $this->getPlatform());
350 $values[] = array('label' => 'Platform version', 'value' => $this->getPlatformVersion(true));
351 $values[] = array('label' => 'Platform version name', 'value' => $this->getPlatformVersion());
352 $values[] = array('label' => 'Platform is 64-bit', 'value' => $this->is64bitPlatform() ? 'true' : 'false');
353 $values[] = array('label' => 'Is mobile', 'value' => $this->isMobile() ? 'true' : 'false');
354 $values[] = array('label' => 'Is robot', 'value' => $this->isRobot() ? 'true' : 'false');
355 $values[] = array('label' => 'Robot name', 'value' => $this->isRobot() ? ($this->getRobotName() != self::ROBOT_UNKNOWN ? $this->getRobotName() : 'Unknown') : 'Not applicable');
356 $values[] = array('label' => 'Robot version', 'value' => $this->isRobot() ? ($this->getRobotVersion() != self::ROBOT_VERSION_UNKNOWN ? $this->getRobotVersion() : 'Unknown') : 'Not applicable');
357 $values[] = array('label' => 'IE is in compatibility view', 'value' => $this->isInIECompatibilityView() ? 'true' : 'false');
358 $values[] = array('label' => 'Emulated IE version', 'value' => $this->isInIECompatibilityView() ? $this->getIECompatibilityView() : 'Not applicable');
359 $values[] = array('label' => 'Is Chrome Frame', 'value' => $this->isChromeFrame() ? 'true' : 'false');
360
361 foreach ($values as $currVal) {
362 $result .= '<strong>' . htmlspecialchars($currVal['label'], ENT_NOQUOTES) . ':</strong> ' . $currVal['value'] . '<br />' . PHP_EOL;
363 }
364
365 return $result;
366 }
367
368
369 //--- PUBLIC MEMBERS -----------------------------------------------------------------------------------------------
370
371
372 /**
373 * Dynamically add support for a new Web browser.
374 * @param string $browserName The Web browser name (used for display).
375 * @param mixed $uaNameToLookFor (optional) The string (or array of strings) representing the browser name to find
376 * in the user agent. If omitted, $browserName will be used.
377 * @param boolean $isMobile (optional) Determines if the browser is from a mobile device.
378 * @param string $separator (optional) The separator string used to split the browser name and the version number in
379 * the user agent.
380 * @param boolean $uaNameFindWords (optional) Determines if the browser name to find should match a word instead of
381 * a part of a word. For example "Bar" would not be found in "FooBar" when true but would be found in "Foo Bar".
382 * When set to false, the browser name can be found anywhere in the user agent string.
383 * @see removeCustomBrowserDetection()
384 * @return boolean Returns true if the custom rule has been added, false otherwise.
385 */
386 public function addCustomBrowserDetection($browserName, $uaNameToLookFor = '', $isMobile = false, $separator = '/', $uaNameFindWords = true)
387 {
388 if ($browserName == '') {
389 return false;
390 }
391 if (array_key_exists($browserName, $this->_customBrowserDetection)) {
392 unset($this->_customBrowserDetection[$browserName]);
393 }
394 if ($uaNameToLookFor == '') {
395 $uaNameToLookFor = $browserName;
396 }
397 $this->_customBrowserDetection[$browserName] = array('uaNameToLookFor' => $uaNameToLookFor, 'isMobile' => $isMobile == true,
398 'separator' => $separator, 'uaNameFindWords' => $uaNameFindWords == true);
399 return true;
400 }
401
402 /**
403 * Dynamically add support for a new platform.
404 * @param string $platformName The platform name (used for display).
405 * @param mixed $platformNameToLookFor (optional) The string (or array of strings) representing the platform name to
406 * find in the user agent. If omitted, $platformName will be used.
407 * @param boolean $isMobile (optional) Determines if the platform is from a mobile device.
408 * @param boolean $uaNameFindWords (optional) Determines if the platform name to find should match a word instead of
409 * a part of a word. For example "Bar" would not be found in "FooBar" when true but would be found in "Foo Bar".
410 * @see removeCustomPlatformDetection()
411 * @return boolean Returns true if the custom rule has been added, false otherwise.
412 */
413 public function addCustomPlatformDetection($platformName, $platformNameToLookFor = '', $isMobile = false, $uaNameFindWords = true)
414 {
415 if ($platformName == '') {
416 return false;
417 }
418 if (array_key_exists($platformName, $this->_customPlatformDetection)) {
419 unset($this->_customPlatformDetection[$platformName]);
420 }
421 if ($platformNameToLookFor == '') {
422 $platformNameToLookFor = $platformName;
423 }
424 $this->_customPlatformDetection[$platformName] = array('platformNameToLookFor' => $platformNameToLookFor,
425 'isMobile' => $isMobile == true,
426 'uaNameFindWords' => $uaNameFindWords == true);
427 return true;
428 }
429
430 /**
431 * Dynamically add support for a new robot.
432 * @param string $robotName The robot name (used for display).
433 * @param mixed $uaNameToLookFor (optional) The string (or array of strings) representing the robot name to find
434 * in the user agent. If omitted, $robotName will be used.
435 * @param boolean $isMobile (optional) Determines if the robot should be considered as mobile or not.
436 * @param string $separator (optional) The separator string used to split the robot name and the version number in
437 * the user agent.
438 * @param boolean $uaNameFindWords (optional) Determines if the robot name to find should match a word instead of
439 * a part of a word. For example "Bar" would not be found in "FooBar" when true but would be found in "Foo Bar".
440 * When set to false, the robot name can be found anywhere in the user agent string.
441 * @see removeCustomRobotDetection()
442 * @return boolean Returns true if the custom rule has been added, false otherwise.
443 */
444 public function addCustomRobotDetection($robotName, $uaNameToLookFor = '', $isMobile = false, $separator = '/', $uaNameFindWords = true)
445 {
446 if ($robotName == '') {
447 return false;
448 }
449 if (array_key_exists($robotName, $this->_customRobotDetection)) {
450 unset($this->_customRobotDetection[$robotName]);
451 }
452 if ($uaNameToLookFor == '') {
453 $uaNameToLookFor = $robotName;
454 }
455 $this->_customRobotDetection[$robotName] = array('uaNameToLookFor' => $uaNameToLookFor, 'isMobile' => $isMobile == true,
456 'separator' => $separator, 'uaNameFindWords' => $uaNameFindWords == true);
457 return true;
458 }
459
460 /**
461 * Compare two version number strings.
462 * @param string $sourceVer The source version number.
463 * @param string $compareVer The version number to compare with the source version number.
464 * @return int Returns -1 if $sourceVer < $compareVer, 0 if $sourceVer == $compareVer or 1 if $sourceVer >
465 * $compareVer.
466 */
467 public function compareVersions($sourceVer, $compareVer)
468 {
469 $sourceVer = explode('.', $sourceVer);
470 foreach ($sourceVer as $k => $v) {
471 $sourceVer[$k] = $this->parseInt($v);
472 }
473
474 $compareVer = explode('.', $compareVer);
475 foreach ($compareVer as $k => $v) {
476 $compareVer[$k] = $this->parseInt($v);
477 }
478
479 if (count($sourceVer) != count($compareVer)) {
480 if (count($sourceVer) > count($compareVer)) {
481 for ($i = count($compareVer); $i < count($sourceVer); $i++) {
482 $compareVer[$i] = 0;
483 }
484 } else {
485 for ($i = count($sourceVer); $i < count($compareVer); $i++) {
486 $sourceVer[$i] = 0;
487 }
488 }
489 }
490
491 foreach ($sourceVer as $i => $srcVerPart) {
492 if ($srcVerPart > $compareVer[$i]) {
493 return 1;
494 } else {
495 if ($srcVerPart < $compareVer[$i]) {
496 return -1;
497 }
498 }
499 }
500
501 return 0;
502 }
503
504 /**
505 * Get the name and version of the browser emulated in the compatibility view mode (if any). Since Internet
506 * Explorer 8, IE can be put in compatibility mode to make websites that were created for older browsers, especially
507 * IE 6 and 7, look better in IE 8+ which renders web pages closer to the standards and thus differently from those
508 * older versions of IE.
509 * @param boolean $asArray (optional) Determines if the return value must be an array (true) or a string (false).
510 * @return mixed If a string was requested, the function returns the name and version of the browser emulated in
511 * the compatibility view mode or an empty string if the browser is not in compatibility view mode. If an array was
512 * requested, an array with the keys 'browser' and 'version' is returned.
513 */
514 public function getIECompatibilityView($asArray = false)
515 {
516 if ($asArray) {
517 return array('browser' => $this->_compatibilityViewName, 'version' => $this->_compatibilityViewVer);
518 } else {
519 return trim($this->_compatibilityViewName . ' ' . $this->_compatibilityViewVer);
520 }
521 }
522
523 /**
524 * Return the BrowserDetection class version.
525 * @return string Returns the version as a sting with the #.#.# format.
526 */
527 public function getLibVersion()
528 {
529 return '2.9.7';
530 }
531
532 /**
533 * Get the name of the browser. All of the return values are class constants. You can compare them like this:
534 * $myBrowserInstance->getName() == BrowserDetection::BROWSER_FIREFOX.
535 * @return string Returns the name of the browser or BrowserDetection::BROWSER_UNKNOWN if unknown.
536 */
537 public function getName()
538 {
539 return $this->_browserName;
540 }
541
542 /**
543 * Get the name of the platform family on which the browser is run on (such as Windows, Apple, etc.). All of
544 * the return values are class constants. You can compare them like this:
545 * $myBrowserInstance->getPlatform() == BrowserDetection::PLATFORM_ANDROID.
546 * @return string Returns the name of the platform or BrowserDetection::PLATFORM_UNKNOWN if unknown.
547 */
548 public function getPlatform()
549 {
550 return $this->_platform;
551 }
552
553 /**
554 * Get the platform version on which the browser is run on. It can be returned as a string number like 'NT 6.3' or
555 * as a name like 'Windows 8.1'. When returning version string numbers for Windows NT OS families the number is
556 * prefixed by 'NT ' to differentiate from older Windows 3.x & 9x release. At the moment only the Windows and
557 * Android operating systems are supported.
558 * @param boolean $returnVersionNumbers (optional) Determines if the return value must be versions numbers as a
559 * string (true) or the version name (false).
560 * @param boolean $returnServerFlavor (optional) Since some Windows NT versions have the same values, this flag
561 * determines if the Server flavor is returned or not. For instance Windows 8.1 and Windows Server 2012 R2 both use
562 * version 6.3. This parameter is only useful when testing for Windows.
563 * @return string Returns the version name/version numbers of the platform or the constant PLATFORM_VERSION_UNKNOWN
564 * if unknown.
565 */
566 public function getPlatformVersion($returnVersionNumbers = false, $returnServerFlavor = false)
567 {
568 if ($this->_platformVersion == self::PLATFORM_VERSION_UNKNOWN || $this->_platformVersion == '') {
569 return self::PLATFORM_VERSION_UNKNOWN;
570 }
571
572 if ($returnVersionNumbers) {
573 return $this->_platformVersion;
574 } else {
575 switch ($this->getPlatform()) {
576 case self::PLATFORM_WINDOWS:
577 if (substr($this->_platformVersion, 0, 3) == 'NT ') {
578 return $this->windowsNTVerToStr(substr($this->_platformVersion, 3), $returnServerFlavor);
579 } else {
580 return $this->windowsVerToStr($this->_platformVersion);
581 }
582 break;
583
584 case self::PLATFORM_MACINTOSH:
585 return $this->macVerToStr($this->_platformVersion);
586
587 case self::PLATFORM_ANDROID:
588 return $this->androidVerToStr($this->_platformVersion);
589
590 case self::PLATFORM_IOS:
591 return $this->iOSVerToStr($this->_platformVersion);
592
593 default: return self::PLATFORM_VERSION_UNKNOWN;
594 }
595 }
596 }
597
598 /**
599 * Get the name of the robot. All of the return values are class constants. You can compare them like this:
600 * $myBrowserInstance->getRobotName() == BrowserDetection::ROBOT_GOOGLEBOT.
601 * @return string Returns the name of the robot or BrowserDetection::ROBOT_UNKNOWN if unknown.
602 */
603 public function getRobotName()
604 {
605 return $this->_robotName;
606 }
607
608 /**
609 * Get the version of the robot.
610 * @return string Returns the version of the robot or BrowserDetection::ROBOT_VERSION_UNKNOWN if unknown.
611 */
612 public function getRobotVersion()
613 {
614 return $this->_robotVersion;
615 }
616
617 /**
618 * Get the user agent value used by the class to determine the browser details.
619 * @return string The user agent string.
620 */
621 public function getUserAgent()
622 {
623 return $this->_agent;
624 }
625
626 /**
627 * Get the version of the browser.
628 * @return string Returns the version of the browser or BrowserDetection::VERSION_UNKNOWN if unknown.
629 */
630 public function getVersion()
631 {
632 return $this->_version;
633 }
634
635 /**
636 * Determine if the browser is executed from a 64-bit platform. Keep in mind that not all platforms/browsers report
637 * this and the result may not always be accurate.
638 * @return boolean Returns true if the browser is executed from a 64-bit platform.
639 */
640 public function is64bitPlatform()
641 {
642 return $this->_is64bit;
643 }
644
645 /**
646 * Determine if the browser runs Google Chrome Frame (it's a plug-in designed for Internet Explorer 6+ based on the
647 * open-source Chromium project - it's like a Chrome browser within IE).
648 * @return boolean Returns true if the browser is using Google Chrome Frame, false otherwise.
649 */
650 public function isChromeFrame()
651 {
652 return $this->containString($this->_agent, 'chromeframe');
653 }
654
655 /**
656 * Determine if the browser is in compatibility view or not. Since Internet Explorer 8, IE can be put in
657 * compatibility mode to make websites that were created for older browsers, especially IE 6 and 7, look better in
658 * IE 8+ which renders web pages closer to the standards and thus differently from those older versions of IE.
659 * @return boolean Returns true if the browser is in compatibility view, false otherwise.
660 */
661 public function isInIECompatibilityView()
662 {
663 return ($this->_compatibilityViewName != '') || ($this->_compatibilityViewVer != '');
664 }
665
666 /**
667 * Determine if the browser is from a mobile device or not.
668 * @return boolean Returns true if the browser is from a mobile device, false otherwise.
669 */
670 public function isMobile()
671 {
672 return $this->_isMobile;
673 }
674
675 /**
676 * Determine if the browser is a robot (Googlebot, Bingbot, Yahoo! Slurp...) or not.
677 * @return boolean Returns true if the browser is a robot, false otherwise.
678 */
679 public function isRobot()
680 {
681 return $this->_isRobot;
682 }
683
684 /**
685 * Remove support for a previously added Web browser.
686 * @param string $browserName The Web browser name as used when added.
687 * @see addCustomBrowserDetection()
688 * @return boolean Returns true if the custom rule has been found and removed, false otherwise.
689 */
690 public function removeCustomBrowserDetection($browserName)
691 {
692 if (array_key_exists($browserName, $this->_customBrowserDetection)) {
693 unset($this->_customBrowserDetection[$browserName]);
694 return true;
695 }
696
697 return false;
698 }
699
700 /**
701 * Remove support for a previously added platform.
702 * @param string $platformName The platform name as used when added.
703 * @see addCustomPlatformDetection()
704 * @return boolean Returns true if the custom rule has been found and removed, false otherwise.
705 */
706 public function removeCustomPlatformDetection($platformName)
707 {
708 if (array_key_exists($platformName, $this->_customPlatformDetection)) {
709 unset($this->_customPlatformDetection[$platformName]);
710 return true;
711 }
712
713 return false;
714 }
715
716 /**
717 * Remove support for a previously added robot.
718 * @param string $robotName The robot name as used when added.
719 * @see addCustomRobotDetection()
720 * @return boolean Returns true if the custom rule has been found and removed, false otherwise.
721 */
722 public function removeCustomRobotDetection($robotName)
723 {
724 if (array_key_exists($robotName, $this->_customRobotDetection)) {
725 unset($this->_customRobotDetection[$robotName]);
726 return true;
727 }
728
729 return false;
730 }
731
732 /**
733 * Set the user agent to use with the class.
734 * @param string $agentString (optional) The value of the user agent. If an empty string is sent (default),
735 * $_SERVER['HTTP_USER_AGENT'] will be used.
736 */
737 public function setUserAgent($agentString = '')
738 {
739 if (!is_string($agentString) || trim($agentString) == '') {
740 //https://bugs.php.net/bug.php?id=49184
741 if (filter_has_var(INPUT_SERVER, 'HTTP_USER_AGENT')) {
742 $agentString = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
743 } else if (array_key_exists('HTTP_USER_AGENT', $_SERVER) && is_string($_SERVER['HTTP_USER_AGENT'])) {
744 $agentString = filter_var($_SERVER['HTTP_USER_AGENT'], FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
745 } else {
746 $agentString = '';
747 }
748
749 if ($agentString === false || $agentString === NULL) {
750 //filter_input or filter_var failed
751 $agentString = '';
752 }
753 }
754
755 $this->reset();
756 $this->_agent = $agentString;
757 $this->detect();
758 }
759
760
761 //--- PROTECTED MEMBERS --------------------------------------------------------------------------------------------
762
763
764 /**
765 * Convert the Android version numbers to the operating system name. For instance '1.6' returns 'Donut'.
766 * @access protected
767 * @param string $androidVer The Android version numbers as a string.
768 * @return string The operating system name or the constant PLATFORM_VERSION_UNKNOWN if nothing match the version
769 * numbers.
770 */
771 protected function androidVerToStr($androidVer)
772 {
773 //https://en.wikipedia.org/wiki/Android_version_history
774
775 if ($this->compareVersions($androidVer, '10') >= 0) {
776 $majorVer = strstr($androidVer, '.', true);
777 if ($majorVer == '') {
778 $majorVer = $androidVer;
779 }
780 return self::BROWSER_ANDROID . ' ' . $majorVer;
781 } else if ($this->compareVersions($androidVer, '9') >= 0 && $this->compareVersions($androidVer, '10') < 0) {
782 return 'Pie';
783 } else if ($this->compareVersions($androidVer, '8') >= 0 && $this->compareVersions($androidVer, '9') < 0) {
784 return 'Oreo';
785 } else if ($this->compareVersions($androidVer, '7') >= 0 && $this->compareVersions($androidVer, '8') < 0) {
786 return 'Nougat';
787 } else if ($this->compareVersions($androidVer, '6') >= 0 && $this->compareVersions($androidVer, '7') < 0) {
788 return 'Marshmallow';
789 } else if ($this->compareVersions($androidVer, '5') >= 0 && $this->compareVersions($androidVer, '5.2') < 0) {
790 return 'Lollipop';
791 } else if ($this->compareVersions($androidVer, '4.4') >= 0 && $this->compareVersions($androidVer, '4.5') < 0) {
792 return 'KitKat';
793 } else if ($this->compareVersions($androidVer, '4.1') >= 0 && $this->compareVersions($androidVer, '4.4') < 0) {
794 return 'Jelly Bean';
795 } else if ($this->compareVersions($androidVer, '4') >= 0 && $this->compareVersions($androidVer, '4.1') < 0) {
796 return 'Ice Cream Sandwich';
797 } else if ($this->compareVersions($androidVer, '3') >= 0 && $this->compareVersions($androidVer, '3.3') < 0) {
798 return 'Honeycomb';
799 } else if ($this->compareVersions($androidVer, '2.3') >= 0 && $this->compareVersions($androidVer, '2.4') < 0) {
800 return 'Gingerbread';
801 } else if ($this->compareVersions($androidVer, '2.2') >= 0 && $this->compareVersions($androidVer, '2.3') < 0) {
802 return 'Froyo';
803 } else if ($this->compareVersions($androidVer, '2') >= 0 && $this->compareVersions($androidVer, '2.2') < 0) {
804 return 'Eclair';
805 } else if ($this->compareVersions($androidVer, '1.6') == 0) {
806 return 'Donut';
807 } else if ($this->compareVersions($androidVer, '1.5') == 0) {
808 return 'Cupcake';
809 } else {
810 return self::PLATFORM_VERSION_UNKNOWN; //Unknown/unnamed Android version
811 }
812 }
813
814 /**
815 * Determine if the browser is the Android browser (based on the WebKit layout engine and coupled with Chrome's
816 * JavaScript engine) or not.
817 * @access protected
818 * @return boolean Returns true if the browser is the Android browser, false otherwise.
819 */
820 protected function checkBrowserAndroid()
821 {
822 //Android don't use the standard "Android/1.0", it uses "Android 1.0;" instead
823 return $this->checkSimpleBrowserUA('Android', $this->_agent, self::BROWSER_ANDROID, true);
824 }
825
826 /**
827 * Determine if the browser is the BlackBerry browser or not.
828 * @access protected
829 * @link https://web.archive.org/web/20170328000854/http://supportforums.blackberry.com/t5/Web-and-WebWorks-Development/How-to-detect-the-BlackBerry-Browser/ta-p/559862
830 * @return boolean Returns true if the browser is the BlackBerry browser, false otherwise.
831 */
832 protected function checkBrowserBlackBerry()
833 {
834 $found = false;
835
836 //Tablet OS check
837 if ($this->checkSimpleBrowserUA('RIM Tablet OS', $this->_agent, self::BROWSER_TABLET_OS, true)) {
838 return true;
839 }
840
841 //Version 6, 7 & 10 check (versions 8 & 9 does not exists)
842 if ($this->checkBrowserUAWithVersion(array('BlackBerry', 'BB10'), $this->_agent, self::BROWSER_BLACKBERRY, true)) {
843 if ($this->getVersion() == self::VERSION_UNKNOWN) {
844 $found = true;
845 } else {
846 return true;
847 }
848 }
849
850 //Version 4.2 to 5.0 check
851 if ($this->checkSimpleBrowserUA('BlackBerry', $this->_agent, self::BROWSER_BLACKBERRY, true, '/', false)) {
852 if ($this->getVersion() == self::VERSION_UNKNOWN) {
853 $found = true;
854 } else {
855 return true;
856 }
857 }
858
859 return $found;
860 }
861
862 /**
863 * Determine if the browser is Chrome or not.
864 * @access protected
865 * @link https://www.google.com/chrome/
866 * @return boolean Returns true if the browser is Chrome, false otherwise.
867 */
868 protected function checkBrowserChrome()
869 {
870 return $this->checkSimpleBrowserUA(array('Chrome', 'CriOS'), $this->_agent, self::BROWSER_CHROME);
871 }
872
873 /**
874 * Determine if the browser is among the custom browser rules or not. Rules are checked in the order they were
875 * added.
876 * @access protected
877 * @return boolean Returns true if we found the browser we were looking for in the custom rules, false otherwise.
878 */
879 protected function checkBrowserCustom()
880 {
881 foreach ($this->_customBrowserDetection as $browserName => $customBrowser) {
882 $uaNameToLookFor = $customBrowser['uaNameToLookFor'];
883 $isMobile = $customBrowser['isMobile'];
884 $separator = $customBrowser['separator'];
885 $uaNameFindWords = $customBrowser['uaNameFindWords'];
886 if ($this->checkSimpleBrowserUA($uaNameToLookFor, $this->_agent, $browserName, $isMobile, $separator, $uaNameFindWords)) {
887 return true;
888 }
889 }
890 return false;
891 }
892
893 /**
894 * Determine if the browser is Edge or not.
895 * @access protected
896 * @return boolean Returns true if the browser is Edge, false otherwise.
897 */
898 protected function checkBrowserEdge()
899 {
900 return $this->checkSimpleBrowserUA(array('Edg', 'Edge', 'EdgA'), $this->_agent, self::BROWSER_EDGE);
901 }
902
903 /**
904 * Determine if the browser is Firebird or not. Firebird was the name of Firefox from version 0.6 to 0.7.1.
905 * @access protected
906 * @return boolean Returns true if the browser is Firebird, false otherwise.
907 */
908 protected function checkBrowserFirebird()
909 {
910 return $this->checkSimpleBrowserUA('Firebird', $this->_agent, self::BROWSER_FIREBIRD);
911 }
912
913 /**
914 * Determine if the browser is Firefox or not.
915 * @access protected
916 * @link https://www.mozilla.org/en-US/firefox/new/
917 * @return boolean Returns true if the browser is Firefox, false otherwise.
918 */
919 protected function checkBrowserFirefox()
920 {
921 //Safari heavily matches with Firefox, ensure that Safari is filtered out...
922 if (preg_match('/.*Firefox[ (\/]*([a-z0-9.-]*)/i', $this->_agent, $matches) &&
923 !$this->containString($this->_agent, 'Safari')) {
924 $this->setBrowser(self::BROWSER_FIREFOX);
925 $this->setVersion($matches[1]);
926 $this->setMobile(false);
927 $this->setRobot(false);
928
929 return true;
930 }
931
932 return false;
933 }
934
935 /**
936 * Determine if the browser is iCab or not.
937 * @access protected
938 * @link http://www.icab.de/
939 * @return boolean Returns true if the browser is iCab, false otherwise.
940 */
941 protected function checkBrowserIcab()
942 {
943 //Some (early) iCab versions don't use the standard "iCab/1.0", they uses "iCab 1.0;" instead
944 return $this->checkSimpleBrowserUA('iCab', $this->_agent, self::BROWSER_ICAB);
945 }
946
947 /**
948 * Determine if the browser is GNU IceCat (formerly known as GNU IceWeasel) or not.
949 * @access protected
950 * @link https://www.gnu.org/software/gnuzilla/
951 * @return boolean Returns true if the browser is GNU IceCat, false otherwise.
952 */
953 protected function checkBrowserIceCat()
954 {
955 return $this->checkSimpleBrowserUA('IceCat', $this->_agent, self::BROWSER_ICECAT);
956 }
957
958 /**
959 * Determine if the browser is GNU IceWeasel (now know as GNU IceCat) or not.
960 * @access protected
961 * @see checkBrowserIceCat()
962 * @return boolean Returns true if the browser is GNU IceWeasel, false otherwise.
963 */
964 protected function checkBrowserIceWeasel()
965 {
966 return $this->checkSimpleBrowserUA('Iceweasel', $this->_agent, self::BROWSER_ICEWEASEL);
967 }
968
969 /**
970 * Determine if the browser is Internet Explorer or not.
971 * @access protected
972 * @link https://www.microsoft.com/ie/
973 * @link https://en.wikipedia.org/wiki/Internet_Explorer_Mobile
974 * @return boolean Returns true if the browser is Internet Explorer, false otherwise.
975 */
976 protected function checkBrowserInternetExplorer()
977 {
978 //Test for Internet Explorer Mobile (formerly Pocket Internet Explorer)
979 if ($this->checkSimpleBrowserUA(array('IEMobile', 'MSPIE'), $this->_agent, self::BROWSER_IE_MOBILE, true)) {
980 return true;
981 }
982
983 //Several browsers uses IE compatibility UAs filter these browsers out (but after testing for IE Mobile)
984 if ($this->containString($this->_agent, 'Opera') || $this->containString($this->_agent, array('BlackBerry', 'Nokia'), true, false)) {
985 return false;
986 }
987
988 //Test for Internet Explorer 1
989 if ($this->checkSimpleBrowserUA('Microsoft Internet Explorer', $this->_agent, self::BROWSER_IE)) {
990 if ($this->getVersion() == self::VERSION_UNKNOWN) {
991 if (preg_match('/308|425|426|474|0b1/i', $this->_agent)) {
992 $this->setVersion('1.5');
993 } else {
994 $this->setVersion('1.0');
995 }
996 }
997 return true;
998 }
999
1000 //Test for Internet Explorer 2+
1001 if ($this->containString($this->_agent, array('MSIE', 'Trident'))) {
1002 $version = '';
1003
1004 if ($this->containString($this->_agent, 'Trident')) {
1005 //Test for Internet Explorer 11+ (check the rv: string)
1006 if ($this->containString($this->_agent, 'rv:', true, false)) {
1007 if ($this->checkSimpleBrowserUA('Trident', $this->_agent, self::BROWSER_IE, false, 'rv:')) {
1008 return true;
1009 }
1010 } else {
1011 //Test for Internet Explorer 8, 9 & 10 (check the Trident string)
1012 if (preg_match('/Trident\/([\d]+)/i', $this->_agent, $foundVersion)) {
1013 //Trident started with version 4.0 on IE 8
1014 $verFromTrident = $this->parseInt($foundVersion[1]) + 4;
1015 if ($verFromTrident >= 8) {
1016 $version = $verFromTrident . '.0';
1017 }
1018 }
1019 }
1020
1021 //If we have the IE version from Trident, we can check for the compatibility view mode
1022 if ($version != '') {
1023 $emulatedVer = '';
1024 preg_match_all('/MSIE\s*([^\s;$]+)/i', $this->_agent, $foundVersions);
1025 foreach ($foundVersions[1] as $currVer) {
1026 //Keep the lowest MSIE version for the emulated version (in compatibility view mode)
1027 if ($emulatedVer == '' || $this->compareVersions($emulatedVer, $currVer) == 1) {
1028 $emulatedVer = $currVer;
1029 }
1030 }
1031 //Set the compatibility view mode if $version != $emulatedVer
1032 if ($this->compareVersions($version, $emulatedVer) != 0) {
1033 $this->_compatibilityViewName = self::BROWSER_IE;
1034 $this->_compatibilityViewVer = $this->cleanVersion($emulatedVer);
1035 }
1036 }
1037 }
1038
1039 //Test for Internet Explorer 2-7 versions if needed
1040 if ($version == '') {
1041 preg_match_all('/MSIE\s+([^\s;$]+)/i', $this->_agent, $foundVersions);
1042 foreach ($foundVersions[1] as $currVer) {
1043 //Keep the highest MSIE version
1044 if ($version == '' || $this->compareVersions($version, $currVer) == -1) {
1045 $version = $currVer;
1046 }
1047 }
1048 }
1049
1050 $this->setBrowser(self::BROWSER_IE);
1051 $this->setVersion($version);
1052 $this->setMobile(false);
1053 $this->setRobot(false);
1054
1055 return true;
1056 }
1057
1058 return false;
1059 }
1060
1061 /**
1062 * Determine if the browser is Konqueror or not.
1063 * @access protected
1064 * @link https://www.konqueror.org/
1065 * @return boolean Returns true if the browser is Konqueror, false otherwise.
1066 */
1067 protected function checkBrowserKonqueror()
1068 {
1069 return $this->checkSimpleBrowserUA('Konqueror', $this->_agent, self::BROWSER_KONQUEROR);
1070 }
1071
1072 /**
1073 * Determine if the browser is Lynx or not. It is the oldest web browser currently in general use and development.
1074 * It is a text-based only Web browser.
1075 * @access protected
1076 * @link https://en.wikipedia.org/wiki/Lynx_(web_browser)
1077 * @return boolean Returns true if the browser is Lynx, false otherwise.
1078 */
1079 protected function checkBrowserLynx()
1080 {
1081 return $this->checkSimpleBrowserUA('Lynx', $this->_agent, self::BROWSER_LYNX);
1082 }
1083
1084 /**
1085 * Determine if the browser is Mozilla or not.
1086 * @access protected
1087 * @return boolean Returns true if the browser is Mozilla, false otherwise.
1088 */
1089 protected function checkBrowserMozilla()
1090 {
1091 return $this->checkSimpleBrowserUA('Mozilla', $this->_agent, self::BROWSER_MOZILLA, false, 'rv:');
1092 }
1093
1094 /**
1095 * Determine if the browser is MSN TV (formerly WebTV) or not.
1096 * @access protected
1097 * @link https://en.wikipedia.org/wiki/MSN_TV
1098 * @return boolean Returns true if the browser is WebTv, false otherwise.
1099 */
1100 protected function checkBrowserMsnTv()
1101 {
1102 return $this->checkSimpleBrowserUA('webtv', $this->_agent, self::BROWSER_MSNTV);
1103 }
1104
1105 /**
1106 * Determine if the browser is Netscape or not. Official support for this browser ended on March 1st, 2008.
1107 * @access protected
1108 * @link https://en.wikipedia.org/wiki/Netscape
1109 * @return boolean Returns true if the browser is Netscape, false otherwise.
1110 */
1111 protected function checkBrowserNetscape()
1112 {
1113 //BlackBerry & Nokia UAs can conflict with Netscape UAs
1114 if ($this->containString($this->_agent, array('BlackBerry', 'Nokia'), true, false)) {
1115 return false;
1116 }
1117
1118 //Netscape v6 to v9 check
1119 if ($this->checkSimpleBrowserUA(array('Netscape', 'Navigator', 'Netscape6'), $this->_agent, self::BROWSER_NETSCAPE)) {
1120 return true;
1121 }
1122
1123 //Netscape v1-4 (v5 don't exists)
1124 $found = false;
1125 if ($this->containString($this->_agent, 'Mozilla') && !$this->containString($this->_agent, 'rv:', true, false)) {
1126 $version = '';
1127 $verParts = explode('/', stristr($this->_agent, 'Mozilla'));
1128 if (count($verParts) > 1) {
1129 $verParts = explode(' ', $verParts[1]);
1130 $verParts = explode('.', $verParts[0]);
1131
1132 $majorVer = $this->parseInt($verParts[0]);
1133 if ($majorVer > 0 && $majorVer < 5) {
1134 $version = implode('.', $verParts);
1135 $found = true;
1136
1137 if (strtolower(substr($version, -4)) == '-sgi') {
1138 $version = substr($version, 0, -4);
1139 } else {
1140 if (strtolower(substr($version, -4)) == 'gold') {
1141 $version = substr($version, 0, -4) . ' Gold'; //Doubles spaces (if any) will be normalized by setVersion()
1142 }
1143 }
1144 }
1145 }
1146 }
1147
1148 if ($found) {
1149 $this->setBrowser(self::BROWSER_NETSCAPE);
1150 $this->setVersion($version);
1151 $this->setMobile(false);
1152 $this->setRobot(false);
1153 }
1154
1155 return $found;
1156 }
1157
1158 /**
1159 * Determine if the browser is a Nokia browser or not.
1160 * @access protected
1161 * @link https://web.archive.org/web/20141012034159/http://www.developer.nokia.com/Community/Wiki/User-Agent_headers_for_Nokia_devices
1162 * @return boolean Returns true if the browser is a Nokia browser, false otherwise.
1163 */
1164 protected function checkBrowserNokia()
1165 {
1166 if ($this->containString($this->_agent, array('Nokia5800', 'Nokia5530', 'Nokia5230'), true, false)) {
1167 $this->setBrowser(self::BROWSER_NOKIA);
1168 $this->setVersion('7.0');
1169 $this->setMobile(true);
1170 $this->setRobot(false);
1171
1172 return true;
1173 }
1174
1175 if ($this->checkSimpleBrowserUA(array('NokiaBrowser', 'BrowserNG', 'Series60', 'S60', 'S40OviBrowser'), $this->_agent, self::BROWSER_NOKIA, true)) {
1176 return true;
1177 }
1178
1179 return false;
1180 }
1181
1182 /**
1183 * Determine if the browser is Opera or not.
1184 * @access protected
1185 * @link https://www.opera.com/
1186 * @link https://www.opera.com/mobile/
1187 * @link https://web.archive.org/web/20140220123653/http://my.opera.com/community/openweb/idopera/
1188 * @return boolean Returns true if the browser is Opera, false otherwise.
1189 */
1190 protected function checkBrowserOpera()
1191 {
1192 if ($this->checkBrowserUAWithVersion('Opera Mobi', $this->_agent, self::BROWSER_OPERA_MOBILE, true)) {
1193 return true;
1194 }
1195
1196 if ($this->checkSimpleBrowserUA('Opera Mini', $this->_agent, self::BROWSER_OPERA_MINI, true)) {
1197 return true;
1198 }
1199
1200 $version = '';
1201 $found = $this->checkBrowserUAWithVersion('Opera', $this->_agent, self::BROWSER_OPERA);
1202 if ($found && $this->getVersion() != self::VERSION_UNKNOWN) {
1203 $version = $this->getVersion();
1204 }
1205
1206 if (!$found || $version == '') {
1207 if ($this->checkSimpleBrowserUA('Opera', $this->_agent, self::BROWSER_OPERA)) {
1208 return true;
1209 }
1210 }
1211
1212 if (!$found && $this->checkSimpleBrowserUA('Chrome', $this->_agent, self::BROWSER_CHROME) ) {
1213 if ($this->checkSimpleBrowserUA('OPR', $this->_agent, self::BROWSER_OPERA)) {
1214 return true;
1215 }
1216 }
1217
1218 return $found;
1219 }
1220
1221 /**
1222 * Determine if the browser is Phoenix or not. Phoenix was the name of Firefox from version 0.1 to 0.5.
1223 * @access protected
1224 * @return boolean Returns true if the browser is Phoenix, false otherwise.
1225 */
1226 protected function checkBrowserPhoenix()
1227 {
1228 return $this->checkSimpleBrowserUA('Phoenix', $this->_agent, self::BROWSER_PHOENIX);
1229 }
1230
1231 /**
1232 * Determine what is the browser used by the user.
1233 * @access protected
1234 * @return boolean Returns true if the browser has been identified, false otherwise.
1235 */
1236 protected function checkBrowser()
1237 {
1238 //Changing the check order can break the class detection results!
1239 return
1240 /* Major browsers and browsers that need to be detected in a special order */
1241 $this->checkBrowserCustom() || /* Customs rules are always checked first */
1242 $this->checkBrowserMsnTv() || /* MSN TV is based on IE so we must check for MSN TV before IE */
1243 $this->checkBrowserInternetExplorer() ||
1244 $this->checkBrowserOpera() || /* Opera must be checked before Firefox, Netscape and Chrome to avoid conflicts */
1245 $this->checkBrowserEdge() || /* Edge must be checked before Firefox, Safari and Chrome to avoid conflicts */
1246 $this->checkBrowserSamsung() || /* Samsung Internet browser must be checked before Chrome and Safari to avoid conflicts */
1247 $this->checkBrowserUC() || /* UC Browser must be checked before Chrome and Safari to avoid conflicts */
1248 $this->checkBrowserChrome() || /* Chrome must be checked before Netscaoe and Mozilla to avoid conflicts */
1249 $this->checkBrowserIcab() || /* Check iCab before Netscape since iCab have Mozilla UAs */
1250 $this->checkBrowserNetscape() || /* Must be checked before Firefox since Netscape 8-9 are based on Firefox */
1251 $this->checkBrowserIceCat() || /* Check IceCat and IceWeasel before Firefox since they are GNU builds of Firefox */
1252 $this->checkBrowserIceWeasel() ||
1253 $this->checkBrowserFirefox() ||
1254 /* Current browsers that don't need to be detected in any special order */
1255 $this->checkBrowserKonqueror() ||
1256 $this->checkBrowserLynx() ||
1257 /* Mobile */
1258 $this->checkBrowserAndroid() ||
1259 $this->checkBrowserBlackBerry() ||
1260 $this->checkBrowserNokia() ||
1261 /* WebKit base check (after most other checks) */
1262 $this->checkBrowserSafari() ||
1263 /* Deprecated browsers that don't need to be detected in any special order */
1264 $this->checkBrowserFirebird() ||
1265 $this->checkBrowserPhoenix() ||
1266 /* Mozilla is such an open standard that it must be checked last */
1267 $this->checkBrowserMozilla();
1268 }
1269
1270 /**
1271 * Determine if the browser is Safari or not.
1272 * @access protected
1273 * @link https://www.apple.com/safari/
1274 * @link https://web.archive.org/web/20080514173941/http://developer.apple.com/internet/safari/uamatrix.html
1275 * @link https://en.wikipedia.org/wiki/Safari_version_history#Release_history
1276 * @return boolean Returns true if the browser is Safari, false otherwise.
1277 */
1278 protected function checkBrowserSafari()
1279 {
1280 $version = '';
1281
1282 //Check for current versions of Safari
1283 $found = $this->checkBrowserUAWithVersion(array('Safari', 'AppleWebKit'), $this->_agent, self::BROWSER_SAFARI);
1284 if ($found && $this->getVersion() != self::VERSION_UNKNOWN) {
1285 $version = $this->getVersion();
1286 }
1287
1288 //Safari 1-2 didn't had a "Version" string in the UA, only a WebKit build and/or Safari build, extract version from these...
1289 if (!$found || $version == '') {
1290 if (preg_match('/.*Safari[ (\/]*([a-z0-9.-]*)/i', $this->_agent, $matches)) {
1291 $version = $this->safariBuildToSafariVer($matches[1]);
1292 $found = true;
1293 }
1294 }
1295 if (!$found || $version == '') {
1296 if (preg_match('/.*AppleWebKit[ (\/]*([a-z0-9.-]*)/i', $this->_agent, $matches)) {
1297 $version = $this->webKitBuildToSafariVer($matches[1]);
1298 $found = true;
1299 }
1300 }
1301
1302 if ($found) {
1303 $this->setBrowser(self::BROWSER_SAFARI);
1304 $this->setVersion($version);
1305 $this->setMobile(false);
1306 $this->setRobot(false);
1307 }
1308
1309 return $found;
1310 }
1311
1312 /**
1313 * Determine if the browser is the Samsung Internet browser or not.
1314 * @access protected
1315 * @return boolean Returns true if the browser is the the Samsung Internet browser, false otherwise.
1316 */
1317 protected function checkBrowserSamsung()
1318 {
1319 return $this->checkSimpleBrowserUA('SamsungBrowser', $this->_agent, self::BROWSER_SAMSUNG, true);
1320 }
1321
1322 /**
1323 * Test the user agent for a specific browser that use a "Version" string (like Safari and Opera). The user agent
1324 * should look like: "Version/1.0 Browser name/123.456" or "Browser name/123.456 Version/1.0".
1325 * @access protected
1326 * @param mixed $uaNameToLookFor The string (or array of strings) representing the browser name to find in the user
1327 * agent.
1328 * @param string $userAgent The user agent string to work with.
1329 * @param string $browserName The literal browser name. Always use a class constant!
1330 * @param boolean $isMobile (optional) Determines if the browser is from a mobile device.
1331 * @param boolean $findWords (optional) Determines if the needle should match a word to be found. For example "Bar"
1332 * would not be found in "FooBar" when true but would be found in "Foo Bar". When set to false, the needle can be
1333 * found anywhere in the haystack.
1334 * @return boolean Returns true if we found the browser we were looking for, false otherwise.
1335 */
1336 protected function checkBrowserUAWithVersion($uaNameToLookFor, $userAgent, $browserName, $isMobile = false, $findWords = true)
1337 {
1338 if (!is_array($uaNameToLookFor)) {
1339 $uaNameToLookFor = array($uaNameToLookFor);
1340 }
1341
1342 foreach ($uaNameToLookFor as $currUANameToLookFor) {
1343 if ($this->containString($userAgent, $currUANameToLookFor, true, $findWords)) {
1344 $version = '';
1345 $verParts = explode('/', stristr($this->_agent, 'Version'));
1346 if (count($verParts) > 1) {
1347 $verParts = explode(' ', $verParts[1]);
1348 $version = $verParts[0];
1349 }
1350
1351 $this->setBrowser($browserName);
1352 $this->setVersion($version);
1353
1354 $this->setMobile($isMobile);
1355
1356 return true;
1357 }
1358 }
1359
1360 return false;
1361 }
1362
1363 /**
1364 * Determine if the browser is UC Browser or not.
1365 * @access protected
1366 * @return boolean Returns true if the browser is UC Browser, false otherwise.
1367 */
1368 protected function checkBrowserUC()
1369 {
1370 return $this->checkSimpleBrowserUA('UCBrowser', $this->_agent, self::BROWSER_UC, true);
1371 }
1372
1373 /**
1374 * Determine the user's platform.
1375 * @access protected
1376 */
1377 protected function checkPlatform()
1378 {
1379 if (!$this->checkPlatformCustom()) { /* Customs rules are always checked first */
1380 /* Mobile platforms */
1381 if ($this->containString($this->_agent, array('Windows Phone', 'IEMobile'))) { /* Check Windows Phone (formerly Windows Mobile) before Windows */
1382 $this->setPlatform(self::PLATFORM_WINDOWS_PHONE);
1383 $this->setMobile(true);
1384 } else if ($this->containString($this->_agent, 'Windows CE')) { /* Check Windows CE before Windows */
1385 $this->setPlatform(self::PLATFORM_WINDOWS_CE);
1386 $this->setMobile(true);
1387 } else if ($this->containString($this->_agent, array('CPU OS', 'CPU iPhone OS', 'iPhone', 'iPad', 'iPod'))) { /* Check iOS (iPad/iPod/iPhone) before Macintosh */
1388 $this->setPlatform(self::PLATFORM_IOS);
1389 $this->setMobile(true);
1390 } else if ($this->containString($this->_agent, array('CrOS', 'Chromebook'))) {
1391 $this->setPlatform(self::PLATFORM_CHROME_OS);
1392 $this->setMobile(true);
1393 } else if ($this->containString($this->_agent, 'Android')) {
1394 $this->setPlatform(self::PLATFORM_ANDROID);
1395 $this->setMobile(true);
1396 } else if ($this->containString($this->_agent, 'BlackBerry', true, false) || $this->containString($this->_agent, array('BB10', 'RIM Tablet OS'))) {
1397 $this->setPlatform(self::PLATFORM_BLACKBERRY);
1398 $this->setMobile(true);
1399 } else if ($this->containString($this->_agent, 'Nokia', true, false)) {
1400 $this->setPlatform(self::PLATFORM_NOKIA);
1401 $this->setMobile(true);
1402
1403 /* Desktop platforms */
1404 } else if ($this->containString($this->_agent, 'Windows')) {
1405 $this->setPlatform(self::PLATFORM_WINDOWS);
1406 } else if ($this->containString($this->_agent, 'Macintosh')) {
1407 $this->setPlatform(self::PLATFORM_MACINTOSH);
1408 } else if ($this->containString($this->_agent, 'Linux')) {
1409 $this->setPlatform(self::PLATFORM_LINUX);
1410 } else if ($this->containString($this->_agent, 'FreeBSD')) {
1411 $this->setPlatform(self::PLATFORM_FREEBSD);
1412 } else if ($this->containString($this->_agent, 'OpenBSD')) {
1413 $this->setPlatform(self::PLATFORM_OPENBSD);
1414 } else if ($this->containString($this->_agent, 'NetBSD')) {
1415 $this->setPlatform(self::PLATFORM_NETBSD);
1416
1417 /* Discontinued */
1418 } else if ($this->containString($this->_agent, array('Symbian', 'SymbianOS'))) {
1419 $this->setPlatform(self::PLATFORM_SYMBIAN);
1420 $this->setMobile(true);
1421 } else if ($this->containString($this->_agent, 'OpenSolaris')) {
1422 $this->setPlatform(self::PLATFORM_OPENSOLARIS);
1423
1424 /* Generic */
1425 } else if ($this->containString($this->_agent, 'Win', true, false)) {
1426 $this->setPlatform(self::PLATFORM_WINDOWS);
1427 } else if ($this->containString($this->_agent, 'Mac', true, false)) {
1428 $this->setPlatform(self::PLATFORM_MACINTOSH);
1429 }
1430 }
1431
1432 //Check if it's a 64-bit platform
1433 if ($this->containString($this->_agent, array('WOW64', 'Win64', 'AMD64', 'x86_64', 'x86-64', 'Aarch64', 'ia64',
1434 'IRIX64', 'ppc64', 'sparc64', 'x64;', 'x64_64'))) {
1435 $this->set64bit(true);
1436 }
1437
1438 $this->checkPlatformVersion();
1439 }
1440
1441 /**
1442 * Determine if the platform is among the custom platform rules or not. Rules are checked in the order they were
1443 * added.
1444 * @access protected
1445 * @return boolean Returns true if we found the platform we were looking for in the custom rules, false otherwise.
1446 */
1447 protected function checkPlatformCustom()
1448 {
1449 foreach ($this->_customPlatformDetection as $platformName => $customPlatform) {
1450 $platformNameToLookFor = $customPlatform['platformNameToLookFor'];
1451 $isMobile = $customPlatform['isMobile'];
1452 $findWords = $customPlatform['uaNameFindWords'];
1453 if ($this->containString($this->_agent, $platformNameToLookFor, true, $findWords)) {
1454 $this->setPlatform($platformName);
1455 if ($isMobile) {
1456 $this->setMobile(true);
1457 }
1458 return true;
1459 }
1460 }
1461
1462 return false;
1463 }
1464
1465 /**
1466 * Determine the user's platform version.
1467 * @access protected
1468 */
1469 protected function checkPlatformVersion()
1470 {
1471 $result = '';
1472 switch ($this->getPlatform()) {
1473 case self::PLATFORM_WINDOWS:
1474 if (preg_match('/Windows NT\s*(\d+(?:\.\d+)*)/i', $this->_agent, $foundVersion)) {
1475 $result = 'NT ' . $foundVersion[1];
1476 } else {
1477 //https://support.microsoft.com/en-us/kb/158238
1478
1479 if ($this->containString($this->_agent, array('Windows XP', 'WinXP', 'Win XP'))) {
1480 $result = '5.1';
1481 } else if ($this->containString($this->_agent, 'Windows 2000', 'Win 2000', 'Win2000')) {
1482 $result = '5.0';
1483 } else if ($this->containString($this->_agent, array('Win 9x 4.90', 'Windows ME', 'WinME', 'Win ME'))) {
1484 $result = '4.90.3000'; //Windows Me version range from 4.90.3000 to 4.90.3000A
1485 } else if ($this->containString($this->_agent, array('Windows 98', 'Win98', 'Win 98'))) {
1486 $result = '4.10'; //Windows 98 version range from 4.10.1998 to 4.10.2222B
1487 } else if ($this->containString($this->_agent, array('Windows 95', 'Win95', 'Win 95'))) {
1488 $result = '4.00'; //Windows 95 version range from 4.00.950 to 4.03.1214
1489 } else if (($foundAt = stripos($this->_agent, 'Windows 3')) !== false) {
1490 $result = '3';
1491 if (preg_match('/\d+(?:\.\d+)*/', substr($this->_agent, $foundAt + strlen('Windows 3')), $foundVersion)) {
1492 $result .= '.' . $foundVersion[0];
1493 }
1494 } else if ($this->containString($this->_agent, 'Win16')) {
1495 $result = '3.1';
1496 }
1497 }
1498 break;
1499
1500 case self::PLATFORM_MACINTOSH:
1501 if (preg_match('/Mac OS X\s*(\d+(?:_\d+)+)/i', $this->_agent, $foundVersion)) {
1502 $result = str_replace('_', '.', $this->cleanVersion($foundVersion[1]));
1503 } else if ($this->containString($this->_agent, 'Mac OS X')) {
1504 $result = '10';
1505 }
1506 break;
1507
1508 case self::PLATFORM_CHROME_OS:
1509 if (preg_match('/CrOS\s*\w*\s*([^\s;$]+)/i', $this->_agent, $foundVersion)) {
1510 $result = $this->cleanVersion($foundVersion[1]);
1511 }
1512 else if (preg_match('/Chromebook\s+([^\s;$]+)/i', $this->_agent, $foundVersion)) {
1513 $result = $this->cleanVersion($foundVersion[1], 'Build');
1514 }
1515 break;
1516
1517 case self::PLATFORM_ANDROID:
1518 if (preg_match('/Android\s+([^\s;$]+)/i', $this->_agent, $foundVersion)) {
1519 $result = $this->cleanVersion($foundVersion[1]);
1520 }
1521 break;
1522
1523 case self::PLATFORM_IOS:
1524 if (preg_match('/(?:CPU OS|iPhone OS|iOS)[\s_]*([\d_]+)/i', $this->_agent, $foundVersion)) {
1525 $result = str_replace('_', '.', $this->cleanVersion($foundVersion[1]));
1526 }
1527 break;
1528 }
1529
1530 if (trim($result) == '') {
1531 $result = self::PLATFORM_VERSION_UNKNOWN;
1532 }
1533 $this->setPlatformVersion($result);
1534 }
1535
1536 /**
1537 * Determine if the robot is the Bingbot crawler or not.
1538 * @access protected
1539 * @link https://www.bing.com/webmaster/help/which-crawlers-does-bing-use-8c184ec0
1540 * @return boolean Returns true if the robot is Bingbot, false otherwise.
1541 */
1542 protected function checkRobotBingbot()
1543 {
1544 return $this->checkSimpleRobot('bingbot', $this->_agent, self::ROBOT_BINGBOT);
1545 }
1546
1547 /**
1548 * Determine if the robot is the Googlebot crawler or not.
1549 * @access protected
1550 * @return boolean Returns true if the robot is Googlebot, false otherwise.
1551 */
1552 protected function checkRobotGooglebot()
1553 {
1554 if ($this->checkSimpleRobot('Googlebot', $this->_agent, self::ROBOT_GOOGLEBOT)) {
1555 if ($this->containString($this->_agent, 'googlebot-mobile')) {
1556 $this->setMobile(true);
1557 }
1558
1559 return true;
1560 }
1561
1562 return false;
1563 }
1564
1565 /**
1566 * Determine if the robot is the MSNBot crawler or not. In October 2010 it was replaced by the Bingbot robot.
1567 * @access protected
1568 * @see checkRobotBingbot()
1569 * @return boolean Returns true if the robot is MSNBot, false otherwise.
1570 */
1571 protected function checkRobotMsnBot()
1572 {
1573 return $this->checkSimpleRobot('msnbot', $this->_agent, self::ROBOT_MSNBOT);
1574 }
1575
1576 /**
1577 * Determine if it's a robot crawling the page and find it's name and version.
1578 * @access protected
1579 */
1580 protected function checkRobot()
1581 {
1582 $this->checkRobotCustom() || /* Customs rules are always checked first */
1583 $this->checkRobotGooglebot() ||
1584 $this->checkRobotBingbot() ||
1585 $this->checkRobotMsnBot() ||
1586 $this->checkRobotSlurp() ||
1587 $this->checkRobotYahooMultimedia() ||
1588 $this->checkRobotW3CValidator();
1589 }
1590
1591 /**
1592 * Determine if the robot is among the custom robot rules or not. Rules are checked in the order they were added.
1593 * @access protected
1594 * @return boolean Returns true if we found the robot we were looking for in the custom rules, false otherwise.
1595 */
1596 protected function checkRobotCustom()
1597 {
1598 foreach ($this->_customRobotDetection as $robotName => $customRobot) {
1599 $uaNameToLookFor = $customRobot['uaNameToLookFor'];
1600 $isMobile = $customRobot['isMobile'];
1601 $separator = $customRobot['separator'];
1602 $uaNameFindWords = $customRobot['uaNameFindWords'];
1603
1604 if ($this->checkSimpleRobot($uaNameToLookFor, $this->_agent, $robotName, $separator, $uaNameFindWords)) {
1605 return true;
1606 }
1607 }
1608 return false;
1609 }
1610
1611 /**
1612 * Determine if the robot is the Yahoo! Slurp crawler or not.
1613 * @access protected
1614 * @return boolean Returns true if the robot is Yahoo! Slurp, false otherwise.
1615 */
1616 protected function checkRobotSlurp()
1617 {
1618 return $this->checkSimpleRobot('Yahoo! Slurp', $this->_agent, self::ROBOT_SLURP);
1619 }
1620
1621 /**
1622 * Determine if the robot is the W3C Validator or not.
1623 * @access protected
1624 * @link https://validator.w3.org/
1625 * @return boolean Returns true if the robot is the W3C Validator, false otherwise.
1626 */
1627 protected function checkRobotW3CValidator()
1628 {
1629 //Since the W3C validates pages with different robots we will prefix our versions with the part validated on the page...
1630
1631 //W3C Link Checker (prefixed with "Link-")
1632 if ($this->checkSimpleRobot('W3C-checklink', $this->_agent, self::ROBOT_W3CVALIDATOR)) {
1633 if ($this->getRobotVersion() != self::ROBOT_VERSION_UNKNOWN) {
1634 $this->setRobotVersion('Link-' . $this->getRobotVersion());
1635 }
1636 return true;
1637 }
1638
1639 //W3C CSS Validation Service (prefixed with "CSS-")
1640 if ($this->checkSimpleRobot('Jigsaw', $this->_agent, self::ROBOT_W3CVALIDATOR)) {
1641 if ($this->getRobotVersion() != self::ROBOT_VERSION_UNKNOWN) {
1642 $this->setRobotVersion('CSS-' . $this->getRobotVersion());
1643 }
1644 return true;
1645 }
1646
1647 //W3C mobileOK Checker (prefixed with "mobileOK-")
1648 if ($this->checkSimpleRobot('W3C-mobileOK', $this->_agent, self::ROBOT_W3CVALIDATOR)) {
1649 if ($this->getRobotVersion() != self::ROBOT_VERSION_UNKNOWN) {
1650 $this->setRobotVersion('mobileOK-' . $this->getRobotVersion());
1651 }
1652 return true;
1653 }
1654
1655 //W3C Markup Validation Service (no prefix)
1656 return $this->checkSimpleRobot('W3C_Validator', $this->_agent, self::ROBOT_W3CVALIDATOR);
1657 }
1658
1659 /**
1660 * Determine if the robot is the Yahoo! multimedia crawler or not.
1661 * @access protected
1662 * @return boolean Returns true if the robot is the Yahoo! multimedia crawler, false otherwise.
1663 */
1664 protected function checkRobotYahooMultimedia()
1665 {
1666 return $this->checkSimpleRobot('Yahoo-MMCrawler', $this->_agent, self::ROBOT_YAHOO_MM);
1667 }
1668
1669 /**
1670 * Test the user agent for a specific browser where the browser name is immediately followed by the version number.
1671 * The user agent should look like: "Browser name/1.0" or "Browser 1.0;".
1672 * @access protected
1673 * @param mixed $uaNameToLookFor The string (or array of strings) representing the browser name to find in the user
1674 * agent.
1675 * @param string $userAgent The user agent string to work with.
1676 * @param string $browserName The literal browser name. Always use a class constant!
1677 * @param boolean $isMobile (optional) Determines if the browser is from a mobile device.
1678 * @param string $separator (optional) The separator string used to split the browser name and the version number in
1679 * the user agent.
1680 * @param boolean $uaNameFindWords (optional) Determines if the browser name to find should match a word instead of
1681 * a part of a word. For example "Bar" would not be found in "FooBar" when true but would be found in "Foo Bar".
1682 * When set to false, the browser name can be found anywhere in the user agent string.
1683 * @return boolean Returns true if we found the browser we were looking for, false otherwise.
1684 */
1685 protected function checkSimpleBrowserUA($uaNameToLookFor, $userAgent, $browserName, $isMobile = false, $separator = '/', $uaNameFindWords = true)
1686 {
1687 if ($this->findAndGetVersion($uaNameToLookFor, $userAgent, $version, $separator, $uaNameFindWords)) {
1688 $this->setBrowser($browserName);
1689 $this->setVersion($version);
1690
1691 $this->setMobile($isMobile);
1692
1693 return true;
1694 }
1695
1696 return false;
1697 }
1698
1699 /**
1700 * Test the user agent for a specific robot where the robot name is immediately followed by the version number.
1701 * The user agent should look like: "Robot name/1.0" or "Robot 1.0;".
1702 * @access protected
1703 * @param mixed $uaNameToLookFor The string (or array of strings) representing the robot name to find in the user
1704 * agent.
1705 * @param string $userAgent The user agent string to work with.
1706 * @param string $robotName The literal robot name. Always use a class constant!
1707 * @param string $separator (optional) The separator string used to split the robot name and the version number in
1708 * the user agent.
1709 * @param boolean $uaNameFindWords (optional) Determines if the robot name to find should match a word instead of
1710 * a part of a word. For example "Bar" would not be found in "FooBar" when true but would be found in "Foo Bar".
1711 * When set to false, the robot name can be found anywhere in the user agent string.
1712 * @return boolean Returns true if we found the robot we were looking for, false otherwise.
1713 */
1714 protected function checkSimpleRobot($uaNameToLookFor, $userAgent, $robotName, $separator = '/', $uaNameFindWords = true)
1715 {
1716 if ($this->findAndGetVersion($uaNameToLookFor, $userAgent, $version, $separator, $uaNameFindWords)) {
1717 $this->setRobot(true);
1718 $this->setRobotName($robotName);
1719 $this->setRobotVersion($version);
1720
1721 return true;
1722 }
1723
1724 return false;
1725 }
1726
1727 /**
1728 * Clean a version string from unwanted characters.
1729 * @access protected
1730 * @param string $version The version string to clean.
1731 * @param mixed $toRemove (optional) String or array of strings representing additional string(s) to remove.
1732 * @return string Returns the cleaned version number string.
1733 */
1734 protected function cleanVersion($version, $toRemove = NULL)
1735 {
1736 $cleanVer = $version;
1737 if ($toRemove !== NULL) {
1738 $cleanVer = str_ireplace($toRemove, '', $cleanVer);
1739 }
1740
1741 //Clear anything that is in parentheses (and the parentheses themselves) - will clear started but unclosed ones too
1742 $cleanVer = preg_replace('/\([^)]+\)?/', '', $cleanVer);
1743 //Replace with a space any character which is NOT an alphanumeric, dot (.), hyphen (-), underscore (_) or space
1744 $cleanVer = preg_replace('/[^0-9.a-zA-Z_ -]/', ' ', $cleanVer);
1745
1746 //Remove trailing and leading spaces
1747 $cleanVer = trim($cleanVer);
1748
1749 //Remove trailing dot (.), hyphen (-), underscore (_)
1750 while (in_array(substr($cleanVer, -1), array('.', '-', '_'))) {
1751 $cleanVer = substr($cleanVer, 0, -1);
1752 }
1753 //Remove leading dot (.), hyphen (-), underscore (_) and character v
1754 while (in_array(substr($cleanVer, 0, 1), array('.', '-', '_', 'v', 'V'))) {
1755 $cleanVer = substr($cleanVer, 1);
1756 }
1757
1758 //Remove double spaces if any
1759 while (strpos($cleanVer, ' ') !== false) {
1760 $cleanVer = str_replace(' ', ' ', $cleanVer);
1761 }
1762
1763 return trim($cleanVer);
1764 }
1765
1766 /**
1767 * Find if one or more substring is contained in a string.
1768 * @access protected
1769 * @param string $haystack The string to search in.
1770 * @param mixed $needle The string to search for. Can be a string or an array of strings if multiples values are to
1771 * be searched.
1772 * @param boolean $insensitive (optional) Determines if we do a case-sensitive search (false) or a case-insensitive
1773 * one (true).
1774 * @param boolean $findWords (optional) Determines if the needle should match a word to be found. For example "Bar"
1775 * would not be found in "FooBar" when true but would be found in "Foo Bar". When set to false, the needle can be
1776 * found anywhere in the haystack.
1777 * @param int $foundPos (optional) Integer buffer that will contain the position of the needle (if found and if a
1778 * non NULL variable has been passed).
1779 * @return boolean Returns true if the needle (or one of the needles) has been found in the haystack, false
1780 * otherwise.
1781 */
1782 protected function containString($haystack, $needle, $insensitive = true, $findWords = true, &$foundPos = NULL)
1783 {
1784 if (!is_array($needle)) {
1785 $needle = array($needle);
1786 }
1787
1788 foreach ($needle as $currNeedle) {
1789 if ($findWords) {
1790 $position = $this->wordPos($haystack, $currNeedle, $insensitive);
1791 } else {
1792 if ($insensitive) {
1793 $position = stripos($haystack, $currNeedle);
1794 } else {
1795 $position = strpos($haystack, $currNeedle);
1796 }
1797 }
1798
1799 if ($position !== false) {
1800 if ($foundPos !== NULL) {
1801 $foundPos = $position;
1802 }
1803 return true;
1804 }
1805 }
1806
1807 return false;
1808 }
1809
1810 /**
1811 * Detect the user environment from the details in the user agent string.
1812 * @access protected
1813 */
1814 protected function detect()
1815 {
1816 $this->checkBrowser();
1817 $this->checkPlatform(); //Check the platform after the browser since some platforms can change the mobile value
1818 $this->checkRobot();
1819 }
1820
1821 /**
1822 * Test the user agent for a specific browser and extract it's version.
1823 * @access protected
1824 * @param type $uaNameToLookFor The string (or array of strings) representing the browser name to find in the user
1825 * agent.
1826 * @param type $userAgent The user agent string to work with.
1827 * @param type $version String buffer that will contain the version found (if any).
1828 * @param type $separator (optional) The separator string used to split the browser name and the version number in
1829 * the user agent.
1830 * @param type $uaNameFindWords (optional) Determines if the browser name to find should match a word instead of
1831 * a part of a word. For example "Bar" would not be found in "FooBar" when true but would be found in "Foo Bar".
1832 * When set to false, the browser name can be found anywhere in the user agent string.
1833 * @return boolean Returns true if we found the browser we were looking for, false otherwise.
1834 */
1835 protected function findAndGetVersion($uaNameToLookFor, $userAgent, &$version, $separator = '/', $uaNameFindWords = true)
1836 {
1837 $version = '';
1838 if (!is_array($uaNameToLookFor)) {
1839 $uaNameToLookFor = array($uaNameToLookFor);
1840 }
1841
1842 foreach ($uaNameToLookFor as $currUANameToLookFor) {
1843 $foundPos = -1;
1844 if ($this->containString($userAgent, $currUANameToLookFor, true, $uaNameFindWords, $foundPos)) {
1845 //Many browsers don't use the standard "Browser/1.0" format, they uses "Browser 1.0;" instead
1846 if (stripos($userAgent, $currUANameToLookFor . $separator) === false) {
1847 $userAgent = str_ireplace($currUANameToLookFor . ' ', $currUANameToLookFor . $separator, $userAgent);
1848 }
1849
1850 $verParts = explode($separator, substr($userAgent, $foundPos));
1851 if (count($verParts) > 1) {
1852 $verParts = explode(' ', $verParts[1]);
1853 $version = $verParts[0];
1854 }
1855
1856 return true;
1857 }
1858 }
1859
1860 return false;
1861 }
1862
1863 /**
1864 * Convert the iOS version numbers to the operating system name. For instance '2.0' returns 'iPhone OS 2.0'.
1865 * @access protected
1866 * @param string $iOSVer The iOS version numbers as a string.
1867 * @return string The operating system name.
1868 */
1869 protected function iOSVerToStr($iOSVer)
1870 {
1871 if ($this->compareVersions($iOSVer, '3.0') <= 0) {
1872 return 'iPhone OS ' . $iOSVer;
1873 } else {
1874 return 'iOS ' . $iOSVer;
1875 }
1876 }
1877
1878 /**
1879 * Convert the macOS version numbers to the operating system name. For instance '10.7' returns 'Mac OS X Lion'.
1880 * @access protected
1881 * @param string $macVer The macOS version numbers as a string.
1882 * @return string The operating system name or the constant PLATFORM_VERSION_UNKNOWN if nothing match the version
1883 * numbers.
1884 */
1885 protected function macVerToStr($macVer)
1886 {
1887 //https://en.wikipedia.org/wiki/OS_X#Release_history
1888
1889 if ($this->_platformVersion === '10') {
1890 return 'Mac OS X'; //Unspecified Mac OS X version
1891 } else if ($this->compareVersions($macVer, '12.0') >= 0 && $this->compareVersions($macVer, '13.0') < 0) {
1892 return 'macOS Monterey';
1893 } else if ($this->compareVersions($macVer, '11.0') >= 0 && $this->compareVersions($macVer, '12.0') < 0) {
1894 return 'macOS Big Sur';
1895 } else if ($this->compareVersions($macVer, '10.15') >= 0 && $this->compareVersions($macVer, '10.16') < 0) {
1896 return 'macOS Catalina';
1897 } else if ($this->compareVersions($macVer, '10.14') >= 0 && $this->compareVersions($macVer, '10.15') < 0) {
1898 return 'macOS Mojave';
1899 } else if ($this->compareVersions($macVer, '10.13') >= 0 && $this->compareVersions($macVer, '10.14') < 0) {
1900 return 'macOS High Sierra';
1901 } else if ($this->compareVersions($macVer, '10.12') >= 0 && $this->compareVersions($macVer, '10.13') < 0) {
1902 return 'macOS Sierra';
1903 } else if ($this->compareVersions($macVer, '10.11') >= 0 && $this->compareVersions($macVer, '10.12') < 0) {
1904 return 'OS X El Capitan';
1905 } else if ($this->compareVersions($macVer, '10.10') >= 0 && $this->compareVersions($macVer, '10.11') < 0) {
1906 return 'OS X Yosemite';
1907 } else if ($this->compareVersions($macVer, '10.9') >= 0 && $this->compareVersions($macVer, '10.10') < 0) {
1908 return 'OS X Mavericks';
1909 } else if ($this->compareVersions($macVer, '10.8') >= 0 && $this->compareVersions($macVer, '10.9') < 0) {
1910 return 'OS X Mountain Lion';
1911 } else if ($this->compareVersions($macVer, '10.7') >= 0 && $this->compareVersions($macVer, '10.8') < 0) {
1912 return 'Mac OS X Lion';
1913 } else if ($this->compareVersions($macVer, '10.6') >= 0 && $this->compareVersions($macVer, '10.7') < 0) {
1914 return 'Mac OS X Snow Leopard';
1915 } else if ($this->compareVersions($macVer, '10.5') >= 0 && $this->compareVersions($macVer, '10.6') < 0) {
1916 return 'Mac OS X Leopard';
1917 } else if ($this->compareVersions($macVer, '10.4') >= 0 && $this->compareVersions($macVer, '10.5') < 0) {
1918 return 'Mac OS X Tiger';
1919 } else if ($this->compareVersions($macVer, '10.3') >= 0 && $this->compareVersions($macVer, '10.4') < 0) {
1920 return 'Mac OS X Panther';
1921 } else if ($this->compareVersions($macVer, '10.2') >= 0 && $this->compareVersions($macVer, '10.3') < 0) {
1922 return 'Mac OS X Jaguar';
1923 } else if ($this->compareVersions($macVer, '10.1') >= 0 && $this->compareVersions($macVer, '10.2') < 0) {
1924 return 'Mac OS X Puma';
1925 } else if ($this->compareVersions($macVer, '10.0') >= 0 && $this->compareVersions($macVer, '10.1') < 0) {
1926 return 'Mac OS X Cheetah';
1927 } else {
1928 return self::PLATFORM_VERSION_UNKNOWN; //Unknown/unnamed Mac OS version
1929 }
1930 }
1931
1932 /**
1933 * Get the integer value of a string variable.
1934 * @access protected
1935 * @param string $intStr The scalar value being converted to an integer.
1936 * @return int The integer value of $intStr on success, or 0 on failure.
1937 */
1938 protected function parseInt($intStr)
1939 {
1940 return intval($intStr, 10);
1941 }
1942
1943 /**
1944 * Reset all the properties of the class.
1945 * @access protected
1946 */
1947 protected function reset()
1948 {
1949 $this->_agent = '';
1950 $this->_browserName = self::BROWSER_UNKNOWN;
1951 $this->_compatibilityViewName = '';
1952 $this->_compatibilityViewVer = '';
1953 $this->_is64bit = false;
1954 $this->_isMobile = false;
1955 $this->_isRobot = false;
1956 $this->_platform = self::PLATFORM_UNKNOWN;
1957 $this->_platformVersion = self::PLATFORM_VERSION_UNKNOWN;
1958 $this->_robotName = self::ROBOT_UNKNOWN;
1959 $this->_robotVersion = self::ROBOT_VERSION_UNKNOWN;
1960 $this->_version = self::VERSION_UNKNOWN;
1961 }
1962
1963 /**
1964 * Convert a Safari build number to a Safari version number.
1965 * @access protected
1966 * @param string $version A string representing the version number.
1967 * @link https://web.archive.org/web/20080514173941/http://developer.apple.com/internet/safari/uamatrix.html
1968 * @return string Returns the Safari version string. If the version can't be determined, an empty string is
1969 * returned.
1970 */
1971 protected function safariBuildToSafariVer($version)
1972 {
1973 $verParts = explode('.', $version);
1974
1975 //We need a 3 parts version (version 2 will becomes 2.0.0)
1976 while (count($verParts) < 3) {
1977 $verParts[] = 0;
1978 }
1979 foreach ($verParts as $i => $currPart) {
1980 $verParts[$i] = $this->parseInt($currPart);
1981 }
1982
1983 switch ($verParts[0]) {
1984 case 419: $result = '2.0.4';
1985 break;
1986 case 417: $result = '2.0.3';
1987 break;
1988 case 416: $result = '2.0.2';
1989 break;
1990
1991 case 412:
1992 if ($verParts[1] >= 5) {
1993 $result = '2.0.1';
1994 } else {
1995 $result = '2.0';
1996 }
1997 break;
1998
1999 case 312:
2000 if ($verParts[1] >= 5) {
2001 $result = '1.3.2';
2002 } else {
2003 if ($verParts[1] >= 3) {
2004 $result = '1.3.1';
2005 } else {
2006 $result = '1.3';
2007 }
2008 }
2009 break;
2010
2011 case 125:
2012 if ($verParts[1] >= 11) {
2013 $result = '1.2.4';
2014 } else {
2015 if ($verParts[1] >= 9) {
2016 $result = '1.2.3';
2017 } else {
2018 if ($verParts[1] >= 7) {
2019 $result = '1.2.2';
2020 } else {
2021 $result = '1.2';
2022 }
2023 }
2024 }
2025 break;
2026
2027 case 100:
2028 if ($verParts[1] >= 1) {
2029 $result = '1.1.1';
2030 } else {
2031 $result = '1.1';
2032 }
2033 break;
2034
2035 case 85:
2036 if ($verParts[1] >= 8) {
2037 $result = '1.0.3';
2038 } else {
2039 if ($verParts[1] >= 7) {
2040 $result = '1.0.2';
2041 } else {
2042 $result = '1.0';
2043 }
2044 }
2045 break;
2046
2047 case 73: $result = '0.9';
2048 break;
2049 case 51: $result = '0.8.1';
2050 break;
2051 case 48: $result = '0.8';
2052 break;
2053
2054 default: $result = '';
2055 }
2056
2057 return $result;
2058 }
2059
2060 /**
2061 * Set if the browser is executed from a 64-bit platform.
2062 * @access protected
2063 * @param boolean $is64bit Value that tells if the browser is executed from a 64-bit platform.
2064 */
2065 protected function set64bit($is64bit)
2066 {
2067 $this->_is64bit = $is64bit == true;
2068 }
2069
2070 /**
2071 * Set the name of the browser.
2072 * @access protected
2073 * @param string $browserName The name of the browser.
2074 */
2075 protected function setBrowser($browserName)
2076 {
2077 $this->_browserName = $browserName;
2078 }
2079
2080 /**
2081 * Set the browser to be from a mobile device or not.
2082 * @access protected
2083 * @param boolean $isMobile (optional) Value that tells if the browser is on a mobile device or not.
2084 */
2085 protected function setMobile($isMobile = true)
2086 {
2087 $this->_isMobile = $isMobile == true;
2088 }
2089
2090 /**
2091 * Set the platform on which the browser is on.
2092 * @access protected
2093 * @param string $platform The name of the platform.
2094 */
2095 protected function setPlatform($platform)
2096 {
2097 $this->_platform = $platform;
2098 }
2099
2100 /**
2101 * Set the platform version on which the browser is on.
2102 * @access protected
2103 * @param string $platformVer The version numbers of the platform.
2104 */
2105 protected function setPlatformVersion($platformVer)
2106 {
2107 $this->_platformVersion = $platformVer;
2108 }
2109
2110 /**
2111 * Set the browser to be a robot (crawler) or not.
2112 * @access protected
2113 * @param boolean $isRobot (optional) Value that tells if the browser is a robot or not.
2114 */
2115 protected function setRobot($isRobot = true)
2116 {
2117 $this->_isRobot = $isRobot == true;
2118 }
2119
2120 /**
2121 * Set the name of the robot.
2122 * @access protected
2123 * @param string $robotName The name of the robot.
2124 */
2125 protected function setRobotName($robotName)
2126 {
2127 $this->_robotName = $robotName;
2128 }
2129
2130 /**
2131 * Set the version of the robot.
2132 * @access protected
2133 * @param string $robotVersion The version of the robot.
2134 */
2135 protected function setRobotVersion($robotVersion)
2136 {
2137 $cleanVer = $this->cleanVersion($robotVersion);
2138
2139 if ($cleanVer == '') {
2140 $this->_robotVersion = self::ROBOT_VERSION_UNKNOWN;
2141 } else {
2142 $this->_robotVersion = $cleanVer;
2143 }
2144 }
2145
2146 /**
2147 * Set the version of the browser.
2148 * @access protected
2149 * @param string $version The version of the browser.
2150 */
2151 protected function setVersion($version)
2152 {
2153 $cleanVer = $this->cleanVersion($version);
2154
2155 if ($cleanVer == '') {
2156 $this->_version = self::VERSION_UNKNOWN;
2157 } else {
2158 $this->_version = $cleanVer;
2159 }
2160 }
2161
2162 /**
2163 * Convert a WebKit build number to a Safari version number.
2164 * @access protected
2165 * @param string $version A string representing the version number.
2166 * @link https://web.archive.org/web/20080514173941/http://developer.apple.com/internet/safari/uamatrix.html
2167 * @return string Returns the Safari version string. If the version can't be determined, an empty string is
2168 * returned.
2169 */
2170 protected function webKitBuildToSafariVer($version)
2171 {
2172 $verParts = explode('.', $version);
2173
2174 //We need a 3 parts version (version 2 will becomes 2.0.0)
2175 while (count($verParts) < 3) {
2176 $verParts[] = 0;
2177 }
2178 foreach ($verParts as $i => $currPart) {
2179 $verParts[$i] = $this->parseInt($currPart);
2180 }
2181
2182 switch ($verParts[0]) {
2183 case 419: $result = '2.0.4';
2184 break;
2185
2186 case 418:
2187 if ($verParts[1] >= 8) {
2188 $result = '2.0.4';
2189 } else {
2190 $result = '2.0.3';
2191 }
2192 break;
2193
2194 case 417: $result = '2.0.3';
2195 break;
2196
2197 case 416: $result = '2.0.2';
2198 break;
2199
2200 case 412:
2201 if ($verParts[1] >= 7) {
2202 $result = '2.0.1';
2203 } else {
2204 $result = '2.0';
2205 }
2206 break;
2207
2208 case 312:
2209 if ($verParts[1] >= 8) {
2210 $result = '1.3.2';
2211 } else {
2212 if ($verParts[1] >= 5) {
2213 $result = '1.3.1';
2214 } else {
2215 $result = '1.3';
2216 }
2217 }
2218 break;
2219
2220 case 125:
2221 if ($this->compareVersions('5.4', $verParts[1] . '.' . $verParts[2]) == -1) {
2222 $result = '1.2.4'; //125.5.5+
2223 } else {
2224 if ($verParts[1] >= 4) {
2225 $result = '1.2.3';
2226 } else {
2227 if ($verParts[1] >= 2) {
2228 $result = '1.2.2';
2229 } else {
2230 $result = '1.2';
2231 }
2232 }
2233 }
2234 break;
2235
2236 //WebKit 100 can be either Safari 1.1 (Safari build 100) or 1.1.1 (Safari build 100.1)
2237 //for this reason, check the Safari build before the WebKit build.
2238 case 100: $result = '1.1.1';
2239 break;
2240
2241 case 85:
2242 if ($verParts[1] >= 8) {
2243 $result = '1.0.3';
2244 } else {
2245 if ($verParts[1] >= 7) {
2246 //WebKit 85.7 can be either Safari 1.0 (Safari build 85.5) or 1.0.2 (Safari build 85.7)
2247 //for this reason, check the Safari build before the WebKit build.
2248 $result = '1.0.2';
2249 } else {
2250 $result = '1.0';
2251 }
2252 }
2253 break;
2254
2255 case 73: $result = '0.9';
2256 break;
2257 case 51: $result = '0.8.1';
2258 break;
2259 case 48: $result = '0.8';
2260 break;
2261
2262 default: $result = '';
2263 }
2264
2265 return $result;
2266 }
2267
2268 /**
2269 * Convert the Windows NT family version numbers to the operating system name. For instance '5.1' returns
2270 * 'Windows XP'.
2271 * @access protected
2272 * @param string $winVer The Windows NT family version numbers as a string.
2273 * @param boolean $returnServerFlavor (optional) Since some Windows NT versions have the same values, this flag
2274 * determines if the Server flavor is returned or not. For instance Windows 8.1 and Windows Server 2012 R2 both use
2275 * version 6.3.
2276 * @return string The operating system name or the constant PLATFORM_VERSION_UNKNOWN if nothing match the version
2277 * numbers.
2278 */
2279 protected function windowsNTVerToStr($winVer, $returnServerFlavor = false)
2280 {
2281 //https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions
2282
2283 $cleanWinVer = explode('.', $winVer);
2284 while (count($cleanWinVer) > 2) {
2285 array_pop($cleanWinVer);
2286 }
2287 $cleanWinVer = implode('.', $cleanWinVer);
2288
2289 if ($this->compareVersions($cleanWinVer, '11') >= 0) {
2290 return self::PLATFORM_WINDOWS . ' ' . $winVer;
2291 } else if ($this->compareVersions($cleanWinVer, '10') >= 0) {
2292 //(Windows Server 2022, 2019 & 2016 have the same version number. Only the build can separate the two - which is not included in the UA)
2293 return $returnServerFlavor ? (self::PLATFORM_WINDOWS . ' Server 2022') : (self::PLATFORM_WINDOWS . ' 10');
2294 } else if ($this->compareVersions($cleanWinVer, '7') < 0) {
2295 if ($this->compareVersions($cleanWinVer, '6.3') == 0) {
2296 return $returnServerFlavor ? (self::PLATFORM_WINDOWS . ' Server 2012 R2') : (self::PLATFORM_WINDOWS . ' 8.1');
2297 } else if ($this->compareVersions($cleanWinVer, '6.2') == 0) {
2298 return $returnServerFlavor ? (self::PLATFORM_WINDOWS . ' Server 2012') : (self::PLATFORM_WINDOWS . ' 8');
2299 } else if ($this->compareVersions($cleanWinVer, '6.1') == 0) {
2300 return $returnServerFlavor ? (self::PLATFORM_WINDOWS . ' Server 2008 R2') : (self::PLATFORM_WINDOWS . ' 7');
2301 } else if ($this->compareVersions($cleanWinVer, '6') == 0) {
2302 return $returnServerFlavor ? (self::PLATFORM_WINDOWS . ' Server 2008') : (self::PLATFORM_WINDOWS . ' Vista');
2303 } else if ($this->compareVersions($cleanWinVer, '5.2') == 0) {
2304 return $returnServerFlavor ? (self::PLATFORM_WINDOWS . ' Server 2003 / ' . self::PLATFORM_WINDOWS . ' Server 2003 R2') : (self::PLATFORM_WINDOWS . ' XP x64 Edition');
2305 } else if ($this->compareVersions($cleanWinVer, '5.1') == 0) {
2306 return self::PLATFORM_WINDOWS . ' XP';
2307 } else if ($this->compareVersions($cleanWinVer, '5') == 0) {
2308 return self::PLATFORM_WINDOWS . ' 2000';
2309 } else if ($this->compareVersions($cleanWinVer, '5') < 0 && $this->compareVersions($cleanWinVer, '3') >= 0) {
2310 return self::PLATFORM_WINDOWS . ' NT ' . $winVer;
2311 }
2312 }
2313
2314 return self::PLATFORM_VERSION_UNKNOWN; //Invalid Windows NT version
2315 }
2316
2317 /**
2318 * Convert the Windows 3.x & 9x family version numbers to the operating system name. For instance '4.10.1998'
2319 * returns 'Windows 98'.
2320 * @access protected
2321 * @param string $winVer The Windows 3.x or 9x family version numbers as a string.
2322 * @return string The operating system name or the constant PLATFORM_VERSION_UNKNOWN if nothing match the version
2323 * numbers.
2324 */
2325 protected function windowsVerToStr($winVer)
2326 {
2327 //https://support.microsoft.com/en-us/kb/158238
2328
2329 if ($this->compareVersions($winVer, '4.90') >= 0 && $this->compareVersions($winVer, '4.91') < 0) {
2330 return self::PLATFORM_WINDOWS . ' Me'; //Normally range from 4.90.3000 to 4.90.3000A
2331 } else if ($this->compareVersions($winVer, '4.10') >= 0 && $this->compareVersions($winVer, '4.11') < 0) {
2332 return self::PLATFORM_WINDOWS . ' 98'; //Normally range from 4.10.1998 to 4.10.2222B
2333 } else if ($this->compareVersions($winVer, '4') >= 0 && $this->compareVersions($winVer, '4.04') < 0) {
2334 return self::PLATFORM_WINDOWS . ' 95'; //Normally range from 4.00.950 to 4.03.1214
2335 } else if ($this->compareVersions($winVer, '3.1') == 0 || $this->compareVersions($winVer, '3.11') == 0) {
2336 return self::PLATFORM_WINDOWS . ' ' . $winVer;
2337 } else if ($this->compareVersions($winVer, '3.10') == 0) {
2338 return self::PLATFORM_WINDOWS . ' 3.1';
2339 } else {
2340 return self::PLATFORM_VERSION_UNKNOWN; //Invalid Windows version
2341 }
2342 }
2343
2344 /**
2345 * Find the position of the first occurrence of a word in a string.
2346 * @access protected
2347 * @param string $haystack The string to search in.
2348 * @param string $needle The string to search for.
2349 * @param boolean $insensitive (optional) Determines if we do a case-sensitive search (false) or a case-insensitive
2350 * one (true).
2351 * @param int $offset If specified, search will start this number of characters counted from the beginning of the
2352 * string. If the offset is negative, the search will start this number of characters counted from the end of the
2353 * string.
2354 * @param string $foundString String buffer that will contain the exact matching needle found. Set to NULL when
2355 * return value of the function is false.
2356 * @return mixed Returns the position of the needle (int) if found, false otherwise. Warning this function may
2357 * return Boolean false, but may also return a non-Boolean value which evaluates to false.
2358 */
2359 protected function wordPos($haystack, $needle, $insensitive = true, $offset = 0, &$foundString = NULL)
2360 {
2361 if ($offset != 0) {
2362 $haystack = substr($haystack, $offset);
2363 }
2364
2365 $parts = explode(' ', $needle);
2366 foreach ($parts as $i => $currPart) {
2367 $parts[$i] = preg_quote($currPart, '/');
2368 }
2369
2370 $regex = '/(?<=\A|[\s\/\\.,;:_()-])' . implode('[\s\/\\.,;:_()-]', $parts) . '(?=[\s\/\\.,;:_()-]|$)/';
2371 if ($insensitive) {
2372 $regex .= 'i';
2373 }
2374
2375 if (preg_match($regex, $haystack, $matches, PREG_OFFSET_CAPTURE)) {
2376 $foundString = $matches[0][0];
2377 return (int)$matches[0][1];
2378 }
2379
2380 return false;
2381 }
2382 }
2383