Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.11% covered (warning)
63.11%
691 / 1095
7.35% covered (danger)
7.35%
5 / 68
CRAP
0.00% covered (danger)
0.00%
0 / 1
User_Agent_Info
63.07% covered (warning)
63.07%
690 / 1094
7.35% covered (danger)
7.35%
5 / 68
9217.23
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 get_mobile_user_agent_name
0.00% covered (danger)
0.00%
0 / 64
0.00% covered (danger)
0.00%
0 / 1
1056
 get_platform
67.57% covered (warning)
67.57%
25 / 37
0.00% covered (danger)
0.00%
0 / 1
29.05
 get_desktop_platform
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
6.02
 get_browser
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
19
 get_browser_display_name
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isTierIphone
33.33% covered (danger)
33.33%
28 / 84
0.00% covered (danger)
0.00%
0 / 1
125.96
 isTierRichCss
32.00% covered (danger)
32.00%
8 / 25
0.00% covered (danger)
0.00%
0 / 1
34.47
 is_tablet
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 is_iphoneOrIpod
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
7.77
 maybe_get_user_agent_from_server
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 is_iphone_or_ipod
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
7.29
 is_chrome_for_iOS
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
4.18
 is_twitter_for_iphone
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 is_twitter_for_ipad
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 is_facebook_for_iphone
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
90
 is_facebook_for_ipad
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 is_wordpress_for_ios
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 is_ipad
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
6.73
 is_firefox_mobile
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 is_firefox_desktop
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 is_firefox_os
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
6.84
 is_safari_browser
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 is_edge_browser
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 is_ie_browser
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 is_opera_desktop
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 is_opera_mobile
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
7.33
 is_opera_mini
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 is_opera_mini_dumb
50.00% covered (danger)
50.00%
5 / 10
0.00% covered (danger)
0.00%
0 / 1
16.00
 is_samsung_browser
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 is_uc_browser
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 is_yandex_browser
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 is_vivaldi_browser
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 is_miui_browser
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 is_silk_browser
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 is_WindowsPhone7
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
9.16
 is_windows_phone_8
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 is_PalmWebOS
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
9.16
 is_TouchPad
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
13.62
 is_S60_OSSBrowser
68.75% covered (warning)
68.75%
11 / 16
0.00% covered (danger)
0.00%
0 / 1
16.39
 is_symbian_platform
64.71% covered (warning)
64.71%
11 / 17
0.00% covered (danger)
0.00%
0 / 1
20.43
 is_symbian_s40_platform
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
7.90
 is_J2ME_platform
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
5.27
 is_MaemoTablet
50.00% covered (danger)
50.00%
6 / 12
0.00% covered (danger)
0.00%
0 / 1
16.00
 is_MeeGo
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
9.16
 is_webkit
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 is_android
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
6.29
 is_android_tablet
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
8.30
 is_kindle_fire
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 is_kindle_touch
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 is_windows8_auth
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 is_wordpress_for_win8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 is_wordpress_desktop_app
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 is_blackberry_tablet
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
4.18
 is_blackbeberry
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
6.29
 is_blackberry_10
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 is_linux_desktop
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 is_mac_desktop
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 is_windows_desktop
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 is_chrome_desktop
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 get_blackbeberry_OS_version
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
306
 detect_blackberry_browser_version
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
156
 is_mobile_app
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 is_Nintendo_3DS
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 is_bot
63.64% covered (warning)
63.64%
7 / 11
0.00% covered (danger)
0.00%
0 / 1
6.20
 is_bot_user_agent
99.64% covered (success)
99.64%
277 / 278
0.00% covered (danger)
0.00%
0 / 1
4
 is_agent
84.21% covered (warning)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 1
8.25
 is_agent_user_agent
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * User agent detection for Jetpack.
4 *
5 * @package automattic/jetpack-device-detection
6 *
7 * We don't want to rename public members.
8 *
9 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
10 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
11 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
12 * @phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
13 */
14
15namespace Automattic\Jetpack\Device_Detection;
16
17require_once __DIR__ . '/functions.php';
18
19/**
20 * A class providing device properties detection.
21 *
22 * Note: str_contains() and other PHP8+ functions that have a polyfill in core are not used here,
23 * as wp-includes/compat.php may not be loaded yet.
24 */
25class User_Agent_Info {
26
27    /**
28     * Provided or fetched User-Agent string.
29     *
30     * @var string
31     */
32    public $useragent;
33
34    /**
35     * A device group matched user agent, e.g. 'iphone'.
36     *
37     * @var string
38     */
39    public $matched_agent;
40
41    /**
42     * Stores whether is the iPhone tier of devices.
43     *
44     * @var bool
45     */
46    public $isTierIphone;
47
48    /**
49     * Stores whether the device can probably support Rich CSS, but JavaScript (jQuery) support is not assumed.
50     *
51     * @var bool
52     */
53    public $isTierRichCss;
54
55    /**
56     * Stores whether it is another mobile device, which cannot be assumed to support CSS or JS (eg, older BlackBerry, RAZR)
57     *
58     * @var bool
59     */
60    public $isTierGenericMobile;
61
62    /**
63     * Stores the device platform name
64     *
65     * @var null|string
66     */
67    private $platform              = null;
68    const PLATFORM_WINDOWS         = 'windows';
69    const PLATFORM_IPHONE          = 'iphone';
70    const PLATFORM_IPOD            = 'ipod';
71    const PLATFORM_IPAD            = 'ipad';
72    const PLATFORM_BLACKBERRY      = 'blackberry';
73    const PLATFORM_BLACKBERRY_10   = 'blackberry_10';
74    const PLATFORM_SYMBIAN         = 'symbian_series60';
75    const PLATFORM_SYMBIAN_S40     = 'symbian_series40';
76    const PLATFORM_J2ME_MIDP       = 'j2me_midp';
77    const PLATFORM_ANDROID         = 'android';
78    const PLATFORM_ANDROID_TABLET  = 'android_tablet';
79    const PLATFORM_FIREFOX_OS      = 'firefoxOS';
80    const PLATFORM_DESKTOP_LINUX   = 'linux';
81    const PLATFORM_DESKTOP_MAC     = 'mac';
82    const PLATFORM_DESKTOP_WINDOWS = 'windows';
83    const PLATFORM_DESKTOP_CHROME  = 'chrome';
84    const BROWSER_CHROME           = 'chrome';
85    const BROWSER_FIREFOX          = 'firefox';
86    const BROWSER_SAFARI           = 'safari';
87    const BROWSER_EDGE             = 'edge';
88    const BROWSER_OPERA            = 'opera';
89    const BROWSER_IE               = 'ie';
90    const BROWSER_SAMSUNG          = 'samsung';
91    const BROWSER_UC               = 'uc';
92    const BROWSER_YANDEX           = 'yandex';
93    const BROWSER_VIVALDI          = 'vivaldi';
94    const BROWSER_MIUI             = 'miui';
95    const BROWSER_SILK             = 'silk';
96    const OTHER                    = 'other';
97
98    const BROWSER_DISPLAY_NAME_MAP = array(
99        self::BROWSER_CHROME  => 'Chrome',
100        self::BROWSER_FIREFOX => 'Firefox',
101        self::BROWSER_SAFARI  => 'Safari',
102        self::BROWSER_EDGE    => 'Edge',
103        self::BROWSER_OPERA   => 'Opera',
104        self::BROWSER_IE      => 'Internet Explorer',
105        self::BROWSER_SAMSUNG => 'Samsung Browser',
106        self::BROWSER_UC      => 'UC Browser',
107        self::BROWSER_YANDEX  => 'Yandex Browser',
108        self::BROWSER_VIVALDI => 'Vivaldi',
109        self::BROWSER_MIUI    => 'MIUI Browser',
110        self::BROWSER_SILK    => 'Amazon Silk',
111        self::OTHER           => 'Other',
112    );
113
114    /**
115     * A list of dumb-phone user agent parts.
116     *
117     * @var array
118     */
119    public $dumb_agents = array(
120        'nokia',
121        'blackberry',
122        'philips',
123        'samsung',
124        'sanyo',
125        'sony',
126        'panasonic',
127        'webos',
128        'ericsson',
129        'alcatel',
130        'palm',
131        'windows ce',
132        'opera mini',
133        'series60',
134        'series40',
135        'au-mic,',
136        'audiovox',
137        'avantgo',
138        'blazer',
139        'danger',
140        'docomo',
141        'epoc',
142        'ericy',
143        'i-mode',
144        'ipaq',
145        'midp-',
146        'mot-',
147        'netfront',
148        'nitro',
149        'palmsource',
150        'pocketpc',
151        'portalmmm',
152        'rover',
153        'sie-',
154        'symbian',
155        'cldc-',
156        'j2me',
157        'smartphone',
158        'up.browser',
159        'up.link',
160        'up.link',
161        'vodafone/',
162        'wap1.',
163        'wap2.',
164        'mobile',
165        'googlebot-mobile',
166    );
167
168    /**
169     * The constructor.
170     *
171     * @param string $ua (Optional) User agent.
172     */
173    public function __construct( $ua = '' ) {
174        if ( $ua ) {
175            $this->useragent = $ua;
176        } elseif ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
177            $this->useragent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This class is all about validating.
178        }
179    }
180
181    /**
182     * This method detects the mobile User Agent name.
183     *
184     * @return string The matched User Agent name, false otherwise.
185     */
186    public function get_mobile_user_agent_name() {
187        if ( static::is_chrome_for_iOS() ) { // Keep this check before the safari rule.
188            return 'chrome-for-ios';
189        } elseif ( static::is_iphone_or_ipod( 'iphone-safari' ) ) {
190            return 'iphone';
191        } elseif ( static::is_ipad( 'ipad-safari' ) ) {
192            return 'ipad';
193        } elseif ( static::is_android_tablet() ) { // Keep this check before the android rule.
194            return 'android_tablet';
195        } elseif ( static::is_android() ) {
196            return 'android';
197        } elseif ( static::is_blackberry_10() ) {
198            return 'blackberry_10';
199        } elseif ( static::is_blackbeberry() ) {
200            return 'blackberry';
201        } elseif ( static::is_WindowsPhone7() ) {
202            return 'win7';
203        } elseif ( static::is_windows_phone_8() ) {
204            return 'winphone8';
205        } elseif ( static::is_opera_mini() ) {
206            return 'opera-mini';
207        } elseif ( static::is_opera_mini_dumb() ) {
208            return 'opera-mini-dumb';
209        } elseif ( static::is_opera_mobile() ) {
210            return 'opera-mobi';
211        } elseif ( static::is_blackberry_tablet() ) {
212            return 'blackberry_tablet';
213        } elseif ( static::is_kindle_fire() ) {
214            return 'kindle-fire';
215        } elseif ( static::is_PalmWebOS() ) {
216            return 'webos';
217        } elseif ( static::is_S60_OSSBrowser() ) {
218            return 'series60';
219        } elseif ( static::is_firefox_os() ) {
220            return 'firefoxOS';
221        } elseif ( static::is_firefox_mobile() ) {
222            return 'firefox_mobile';
223        } elseif ( static::is_MaemoTablet() ) {
224            return 'maemo';
225        } elseif ( static::is_MeeGo() ) {
226            return 'meego';
227        } elseif ( static::is_TouchPad() ) {
228            return 'hp_tablet';
229        } elseif ( static::is_facebook_for_iphone() ) {
230            return 'facebook-for-iphone';
231        } elseif ( static::is_facebook_for_ipad() ) {
232            return 'facebook-for-ipad';
233        } elseif ( static::is_twitter_for_iphone() ) {
234            return 'twitter-for-iphone';
235        } elseif ( static::is_twitter_for_ipad() ) {
236            return 'twitter-for-ipad';
237        } elseif ( static::is_wordpress_for_ios() ) {
238            return 'ios-app';
239        } elseif ( static::is_iphone_or_ipod( 'iphone-not-safari' ) ) {
240            return 'iphone-unknown';
241        } elseif ( static::is_ipad( 'ipad-not-safari' ) ) {
242            return 'ipad-unknown';
243        } elseif ( static::is_Nintendo_3DS() ) {
244            return 'nintendo-3ds';
245        } else {
246            $agent       = $this->useragent;
247            $dumb_agents = $this->dumb_agents;
248            foreach ( $dumb_agents as $dumb_agent ) {
249                if ( false !== strpos( $agent, $dumb_agent ) ) {
250                    return $dumb_agent;
251                }
252            }
253        }
254
255        return false;
256    }
257
258    /**
259     * This method detects the mobile device's platform. All return strings are from the class constants.
260     * Note that this function returns the platform name, not the UA name/type. You should use a different function
261     * if you need to test the UA capabilites.
262     *
263     * @return string|bool Name of the platform, false otherwise.
264     */
265    public function get_platform() {
266        if ( isset( $this->platform ) ) {
267            return $this->platform;
268        }
269
270        if ( empty( $this->useragent ) ) {
271            return false;
272        }
273
274        $ua = strtolower( $this->useragent );
275        if ( strpos( $ua, 'windows phone' ) !== false ) {
276            $this->platform = self::PLATFORM_WINDOWS;
277        } elseif ( strpos( $ua, 'windows ce' ) !== false ) {
278            $this->platform = self::PLATFORM_WINDOWS;
279        } elseif ( strpos( $ua, 'ipad' ) !== false ) {
280            $this->platform = self::PLATFORM_IPAD;
281        } elseif ( strpos( $ua, 'ipod' ) !== false ) {
282            $this->platform = self::PLATFORM_IPOD;
283        } elseif ( strpos( $ua, 'iphone' ) !== false ) {
284            $this->platform = self::PLATFORM_IPHONE;
285        } elseif ( strpos( $ua, 'android' ) !== false ) {
286            if ( static::is_android_tablet() ) {
287                $this->platform = self::PLATFORM_ANDROID_TABLET;
288            } else {
289                $this->platform = self::PLATFORM_ANDROID;
290            }
291        } elseif ( static::is_kindle_fire() ) {
292            $this->platform = self::PLATFORM_ANDROID_TABLET;
293        } elseif ( static::is_blackberry_10() ) {
294            $this->platform = self::PLATFORM_BLACKBERRY_10;
295        } elseif ( strpos( $ua, 'blackberry' ) !== false ) {
296            $this->platform = self::PLATFORM_BLACKBERRY;
297        } elseif ( static::is_blackberry_tablet() ) {
298            $this->platform = self::PLATFORM_BLACKBERRY;
299        } elseif ( static::is_symbian_platform() ) {
300            $this->platform = self::PLATFORM_SYMBIAN;
301        } elseif ( static::is_symbian_s40_platform() ) {
302            $this->platform = self::PLATFORM_SYMBIAN_S40;
303        } elseif ( static::is_J2ME_platform() ) {
304            $this->platform = self::PLATFORM_J2ME_MIDP;
305        } elseif ( static::is_firefox_os() ) {
306            $this->platform = self::PLATFORM_FIREFOX_OS;
307        } else {
308            $this->platform = false;
309        }
310
311        return $this->platform;
312    }
313
314    /**
315     * Returns the platform for desktops
316     *
317     * @return string
318     */
319    public function get_desktop_platform() {
320        $ua = $this->useragent;
321        if ( empty( $ua ) ) {
322            return false;
323        }
324        $platform = self::OTHER;
325
326        if ( static::is_linux_desktop( $ua ) ) {
327            $platform = self::PLATFORM_DESKTOP_LINUX;
328        } elseif ( static::is_mac_desktop( $ua ) ) {
329            $platform = self::PLATFORM_DESKTOP_MAC;
330        } elseif ( static::is_windows_desktop( $ua ) ) {
331            $platform = self::PLATFORM_DESKTOP_WINDOWS;
332        } elseif ( static::is_chrome_desktop( $ua ) ) {
333            $platform = self::PLATFORM_DESKTOP_CHROME;
334        }
335        return $platform;
336    }
337
338    /**
339     * A simple pattern matching method for extracting the browser from the user agent.
340     *
341     * @return string
342     */
343    public function get_browser() {
344        $ua = $this->useragent;
345        if ( empty( $ua ) ) {
346            return self::OTHER;
347        }
348
349        // Check for browsers based on Chromium BEFORE checking for Chrome itself,
350        // as they all include "Chrome" in their user agent string.
351        // Order matters - most specific checks first!
352
353        if ( static::is_samsung_browser( $ua ) ) {
354            return self::BROWSER_SAMSUNG;
355        } elseif ( static::is_yandex_browser( $ua ) ) {
356            return self::BROWSER_YANDEX;
357        } elseif ( static::is_vivaldi_browser( $ua ) ) {
358            return self::BROWSER_VIVALDI;
359        } elseif ( static::is_uc_browser( $ua ) ) {
360            return self::BROWSER_UC;
361        } elseif ( static::is_miui_browser( $ua ) ) {
362            return self::BROWSER_MIUI;
363        } elseif ( static::is_silk_browser( $ua ) ) {
364            return self::BROWSER_SILK;
365        } elseif ( static::is_opera_mini( $ua ) || static::is_opera_mobile( $ua ) || static::is_opera_desktop( $ua ) || static::is_opera_mini_dumb( $ua ) ) {
366            return self::BROWSER_OPERA;
367        } elseif ( static::is_edge_browser( $ua ) ) {
368            return self::BROWSER_EDGE;
369        } elseif ( static::is_chrome_desktop( $ua ) || self::is_chrome_for_iOS( $ua ) ) {
370            return self::BROWSER_CHROME;
371        } elseif ( static::is_safari_browser( $ua ) ) {
372            return self::BROWSER_SAFARI;
373        } elseif ( static::is_firefox_mobile( $ua ) || static::is_firefox_desktop( $ua ) ) {
374            return self::BROWSER_FIREFOX;
375        } elseif ( static::is_ie_browser( $ua ) ) {
376            return self::BROWSER_IE;
377        }
378        return self::OTHER;
379    }
380
381    /**
382     * Get the display name of the browser.
383     *
384     * @return string
385     */
386    public function get_browser_display_name() {
387        $browser = $this->get_browser();
388        return self::BROWSER_DISPLAY_NAME_MAP[ $browser ] ?? $browser;
389    }
390
391    /**
392     * This method detects for UA which can display iPhone-optimized web content.
393     * Includes iPhone, iPod Touch, Android, WebOS, Fennec (Firefox mobile), etc.
394     */
395    public function isTierIphone() {
396        if ( isset( $this->isTierIphone ) ) {
397            return $this->isTierIphone;
398        }
399        if ( $this->is_iphoneOrIpod() ) {
400            $this->matched_agent       = 'iphone';
401            $this->isTierIphone        = true;
402            $this->isTierRichCss       = false;
403            $this->isTierGenericMobile = false;
404        } elseif ( static::is_android() ) {
405            $this->matched_agent       = 'android';
406            $this->isTierIphone        = true;
407            $this->isTierRichCss       = false;
408            $this->isTierGenericMobile = false;
409        } elseif ( static::is_windows_phone_8() ) {
410            $this->matched_agent       = 'winphone8';
411            $this->isTierIphone        = true;
412            $this->isTierRichCss       = false;
413            $this->isTierGenericMobile = false;
414        } elseif ( static::is_WindowsPhone7() ) {
415            $this->matched_agent       = 'win7';
416            $this->isTierIphone        = true;
417            $this->isTierRichCss       = false;
418            $this->isTierGenericMobile = false;
419        } elseif ( static::is_blackberry_10() ) {
420            $this->matched_agent       = 'blackberry-10';
421            $this->isTierIphone        = true;
422            $this->isTierRichCss       = false;
423            $this->isTierGenericMobile = false;
424        } elseif ( static::is_blackbeberry() && 'blackberry-webkit' === static::detect_blackberry_browser_version() ) {
425            $this->matched_agent       = 'blackberry-webkit';
426            $this->isTierIphone        = true;
427            $this->isTierRichCss       = false;
428            $this->isTierGenericMobile = false;
429        } elseif ( static::is_blackberry_tablet() ) {
430            $this->matched_agent       = 'blackberry_tablet';
431            $this->isTierIphone        = true;
432            $this->isTierRichCss       = false;
433            $this->isTierGenericMobile = false;
434        } elseif ( static::is_PalmWebOS() ) {
435            $this->matched_agent       = 'webos';
436            $this->isTierIphone        = true;
437            $this->isTierRichCss       = false;
438            $this->isTierGenericMobile = false;
439        } elseif ( static::is_TouchPad() ) {
440            $this->matched_agent       = 'hp_tablet';
441            $this->isTierIphone        = true;
442            $this->isTierRichCss       = false;
443            $this->isTierGenericMobile = false;
444        } elseif ( static::is_firefox_os() ) {
445            $this->matched_agent       = 'firefoxOS';
446            $this->isTierIphone        = true;
447            $this->isTierRichCss       = false;
448            $this->isTierGenericMobile = false;
449        } elseif ( static::is_firefox_mobile() ) {
450            $this->matched_agent       = 'fennec';
451            $this->isTierIphone        = true;
452            $this->isTierRichCss       = false;
453            $this->isTierGenericMobile = false;
454        } elseif ( static::is_opera_mobile() ) {
455            $this->matched_agent       = 'opera-mobi';
456            $this->isTierIphone        = true;
457            $this->isTierRichCss       = false;
458            $this->isTierGenericMobile = false;
459        } elseif ( static::is_MaemoTablet() ) {
460            $this->matched_agent       = 'maemo';
461            $this->isTierIphone        = true;
462            $this->isTierRichCss       = false;
463            $this->isTierGenericMobile = false;
464        } elseif ( static::is_MeeGo() ) {
465            $this->matched_agent       = 'meego';
466            $this->isTierIphone        = true;
467            $this->isTierRichCss       = false;
468            $this->isTierGenericMobile = false;
469        } elseif ( static::is_kindle_touch() ) {
470            $this->matched_agent       = 'kindle-touch';
471            $this->isTierIphone        = true;
472            $this->isTierRichCss       = false;
473            $this->isTierGenericMobile = false;
474        } elseif ( static::is_Nintendo_3DS() ) {
475            $this->matched_agent       = 'nintendo-3ds';
476            $this->isTierIphone        = true;
477            $this->isTierRichCss       = false;
478            $this->isTierGenericMobile = false;
479        } else {
480            $this->isTierIphone = false;
481        }
482        return $this->isTierIphone;
483    }
484
485    /**
486     * This method detects for UA which are likely to be capable
487     * but may not necessarily support JavaScript.
488     * Excludes all iPhone Tier UA.
489     */
490    public function isTierRichCss() {
491        if ( isset( $this->isTierRichCss ) ) {
492            return $this->isTierRichCss;
493        }
494        if ( $this->isTierIphone() ) {
495            return false;
496        }
497
498        // The following devices are explicitly ok.
499        if ( static::is_S60_OSSBrowser() ) {
500            $this->matched_agent       = 'series60';
501            $this->isTierIphone        = false;
502            $this->isTierRichCss       = true;
503            $this->isTierGenericMobile = false;
504        } elseif ( static::is_opera_mini() ) {
505            $this->matched_agent       = 'opera-mini';
506            $this->isTierIphone        = false;
507            $this->isTierRichCss       = true;
508            $this->isTierGenericMobile = false;
509        } elseif ( static::is_blackbeberry() ) {
510            $detectedDevice = static::detect_blackberry_browser_version();
511            if (
512                'blackberry-5' === $detectedDevice
513                || 'blackberry-4.7' === $detectedDevice
514                || 'blackberry-4.6' === $detectedDevice
515            ) {
516                $this->matched_agent       = $detectedDevice;
517                $this->isTierIphone        = false;
518                $this->isTierRichCss       = true;
519                $this->isTierGenericMobile = false;
520            }
521        } else {
522            $this->isTierRichCss = false;
523        }
524
525        return $this->isTierRichCss;
526    }
527
528    /**
529     * Detects if the user is using a tablet.
530     * props Corey Gilmore, BGR.com
531     *
532     * @return bool
533     */
534    public function is_tablet() {
535        $ua = $this->useragent;
536        return ( 0 // Never true, but makes it easier to manage our list of tablet conditions.
537                || self::is_ipad( $ua )
538                || self::is_android_tablet( $ua )
539                || self::is_blackberry_tablet( $ua )
540                || self::is_kindle_fire( $ua )
541                || self::is_MaemoTablet( $ua )
542                || self::is_TouchPad( $ua )
543        );
544    }
545
546    /**
547     *  Detects if the current UA is the default iPhone or iPod Touch Browser.
548     *
549     *  DEPRECATED: use is_iphone_or_ipod
550     */
551    public function is_iphoneOrIpod() {
552        if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
553            return false;
554        }
555
556        $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
557        if ( ( strpos( $ua, 'iphone' ) !== false ) || ( strpos( $ua, 'ipod' ) !== false ) ) {
558            if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
559                return false;
560            } else {
561                return true;
562            }
563        } else {
564            return false;
565        }
566    }
567
568    /**
569     * Retrieves the user agent from the server if not provided.
570     *
571     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
572     * @return string|false The user agent string or false if not available.
573     */
574    private static function maybe_get_user_agent_from_server( $user_agent = null ) {
575        if ( null !== $user_agent ) {
576            return $user_agent;
577        }
578
579        if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
580            return false;
581        }
582        return wp_unslash( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
583    }
584
585    /**
586     *  Detects if the current UA is iPhone Mobile Safari or another iPhone or iPod Touch Browser.
587     *
588     *  They type can check for any iPhone, an iPhone using Safari, or an iPhone using something other than Safari.
589     *
590     *  Note: If you want to check for Opera mini, Opera mobile or Firefox mobile (or any 3rd party iPhone browser),
591     *  you should put the check condition before the check for 'iphone-any' or 'iphone-not-safari'.
592     *  Otherwise those browsers will be 'catched' by the iphone string.
593     *
594     * @param string      $type       Type of iPhone detection.
595     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
596     */
597    public static function is_iphone_or_ipod( $type = 'iphone-any', $user_agent = null ) {
598
599        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
600        if ( empty( $user_agent ) ) {
601            return false;
602        }
603
604        $ua        = strtolower( wp_unslash( $user_agent ) );
605        $is_iphone = ( strpos( $ua, 'iphone' ) !== false ) || ( strpos( $ua, 'ipod' ) !== false );
606        $is_safari = ( false !== strpos( $ua, 'safari' ) );
607
608        if ( 'iphone-safari' === $type ) {
609            return $is_iphone && $is_safari;
610        } elseif ( 'iphone-not-safari' === $type ) {
611            return $is_iphone && ! $is_safari;
612        } else {
613            return $is_iphone;
614        }
615    }
616
617    /**
618     *  Detects if the current UA is Chrome for iOS
619     *
620     *  The User-Agent string in Chrome for iOS is the same as the Mobile Safari User-Agent, with CriOS/<ChromeRevision> instead of Version/<VersionNum>.
621     *  - Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3
622     *
623     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
624     */
625    public static function is_chrome_for_iOS( $user_agent = null ) {
626        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
627        if ( empty( $user_agent ) ) {
628            return false;
629        }
630
631        if ( self::is_iphone_or_ipod( 'iphone-safari', $user_agent ) === false ) {
632            return false;
633        }
634
635        $ua = strtolower( wp_unslash( $user_agent ) );
636
637        if ( strpos( $ua, 'crios/' ) !== false ) {
638            return true;
639        } else {
640            return false;
641        }
642    }
643
644    /**
645     *  Detects if the current UA is Twitter for iPhone
646     *
647     * Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_5 like Mac OS X; nb-no) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8L1 Twitter for iPhone
648     * Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206 Twitter for iPhone
649     *
650     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
651     */
652    public static function is_twitter_for_iphone( $user_agent = null ) {
653        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
654        if ( empty( $user_agent ) ) {
655            return false;
656        }
657
658        $ua = strtolower( wp_unslash( $user_agent ) );
659
660        if ( strpos( $ua, 'ipad' ) !== false ) {
661            return false;
662        }
663
664        if ( strpos( $ua, 'twitter for iphone' ) !== false ) {
665            return true;
666        } else {
667            return false;
668        }
669    }
670
671    /**
672     * Detects if the current UA is Twitter for iPad
673     *
674     * Old version 4.X - Mozilla/5.0 (iPad; U; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8L1 Twitter for iPad
675     * Ver 5.0 or Higher - Mozilla/5.0 (iPad; CPU OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206 Twitter for iPhone
676     *
677     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
678     */
679    public static function is_twitter_for_ipad( $user_agent = null ) {
680        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
681        if ( empty( $user_agent ) ) {
682            return false;
683        }
684
685        $ua = strtolower( wp_unslash( $user_agent ) );
686
687        if ( strpos( $ua, 'twitter for ipad' ) !== false ) {
688            return true;
689        } elseif ( strpos( $ua, 'ipad' ) !== false && strpos( $ua, 'twitter for iphone' ) !== false ) {
690            return true;
691        } else {
692            return false;
693        }
694    }
695
696    /**
697     * Detects if the current UA is Facebook for iPhone
698     * - Facebook 4020.0 (iPhone; iPhone OS 5.0.1; fr_FR)
699     * - Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_0 like Mac OS X; en_US) AppleWebKit (KHTML, like Gecko) Mobile [FBAN/FBForIPhone;FBAV/4.0.2;FBBV/4020.0;FBDV/iPhone3,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/5.0;FBSS/2; FBCR/O2;FBID/phone;FBLC/en_US;FBSF/2.0]
700     * - Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206 [FBAN/FBIOS;FBAV/5.0;FBBV/47423;FBDV/iPhone3,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/5.1.1;FBSS/2; FBCR/3ITA;FBID/phone;FBLC/en_US]
701     *
702     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
703     */
704    public static function is_facebook_for_iphone( $user_agent = null ) {
705        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
706        if ( empty( $user_agent ) ) {
707            return false;
708        }
709
710        $ua = strtolower( wp_unslash( $user_agent ) );
711
712        if ( false === strpos( $ua, 'iphone' ) ) {
713            return false;
714        }
715
716        if ( false !== strpos( $ua, 'facebook' ) && false === strpos( $ua, 'ipad' ) ) {
717            return true;
718        } elseif ( false !== strpos( $ua, 'fbforiphone' ) && false === strpos( $ua, 'tablet' ) ) {
719            return true;
720        } elseif ( false !== strpos( $ua, 'fban/fbios;' ) && false === strpos( $ua, 'tablet' ) ) { // FB app v5.0 or higher.
721            return true;
722        } else {
723            return false;
724        }
725    }
726
727    /**
728     * Detects if the current UA is Facebook for iPad
729     * - Facebook 4020.0 (iPad; iPhone OS 5.0.1; en_US)
730     * - Mozilla/5.0 (iPad; U; CPU iPhone OS 5_0 like Mac OS X; en_US) AppleWebKit (KHTML, like Gecko) Mobile [FBAN/FBForIPhone;FBAV/4.0.2;FBBV/4020.0;FBDV/iPad2,1;FBMD/iPad;FBSN/iPhone OS;FBSV/5.0;FBSS/1; FBCR/;FBID/tablet;FBLC/en_US;FBSF/1.0]
731     * - Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Mobile/10A403 [FBAN/FBIOS;FBAV/5.0;FBBV/47423;FBDV/iPad2,1;FBMD/iPad;FBSN/iPhone OS;FBSV/6.0;FBSS/1; FBCR/;FBID/tablet;FBLC/en_US]
732     *
733     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
734     */
735    public static function is_facebook_for_ipad( $user_agent = null ) {
736        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
737        if ( empty( $user_agent ) ) {
738            return false;
739        }
740
741        $ua = strtolower( wp_unslash( $user_agent ) );
742
743        if ( false === strpos( $ua, 'ipad' ) ) {
744            return false;
745        }
746
747        if ( false !== strpos( $ua, 'facebook' ) || false !== strpos( $ua, 'fbforiphone' ) || false !== strpos( $ua, 'fban/fbios;' ) ) {
748            return true;
749        } else {
750            return false;
751        }
752    }
753
754    /**
755     *  Detects if the current UA is WordPress for iOS
756     *
757     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
758     */
759    public static function is_wordpress_for_ios( $user_agent = null ) {
760        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
761        if ( empty( $user_agent ) ) {
762            return false;
763        }
764
765        $ua = strtolower( wp_unslash( $user_agent ) );
766        if ( false !== strpos( $ua, 'wp-iphone' ) ) {
767            return true;
768        } else {
769            return false;
770        }
771    }
772
773    /**
774     * Detects if the current device is an iPad.
775     * They type can check for any iPad, an iPad using Safari, or an iPad using something other than Safari.
776     *
777     * Note: If you want to check for Opera mini, Opera mobile or Firefox mobile (or any 3rd party iPad browser),
778     * you should put the check condition before the check for 'iphone-any' or 'iphone-not-safari'.
779     * Otherwise those browsers will be 'catched' by the ipad string.
780     *
781     * @param string      $type       iPad type.
782     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
783     */
784    public static function is_ipad( $type = 'ipad-any', $user_agent = null ) {
785        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
786        if ( empty( $user_agent ) ) {
787            return false;
788        }
789
790        $ua = strtolower( wp_unslash( $user_agent ) );
791
792        $is_ipad   = ( false !== strpos( $ua, 'ipad' ) );
793        $is_safari = ( false !== strpos( $ua, 'safari' ) );
794
795        if ( 'ipad-safari' === $type ) {
796            return $is_ipad && $is_safari;
797        } elseif ( 'ipad-not-safari' === $type ) {
798            return $is_ipad && ! $is_safari;
799        } else {
800            return $is_ipad;
801        }
802    }
803
804    /**
805     * Detects if the current browser is Firefox Mobile (Fennec)
806     *
807     * See http://www.useragentstring.com/pages/Fennec/
808     * Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.1.1) Gecko/20110415 Firefox/4.0.2pre Fennec/4.0.1
809     * Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1b2pre) Gecko/20081015 Fennec/1.0a1
810     *
811     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
812     */
813    public static function is_firefox_mobile( $user_agent = null ) {
814        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
815        if ( empty( $user_agent ) ) {
816            return false;
817        }
818
819        $ua = strtolower( wp_unslash( $user_agent ) );
820
821        if ( strpos( $ua, 'fennec' ) !== false ) {
822            return true;
823        } else {
824            return false;
825        }
826    }
827
828    /**
829     * Detects if the current browser is Firefox for desktop
830     *
831     * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
832     * Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion
833     * The platform section will include 'Mobile' for phones and 'Tablet' for tablets.
834     *
835     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
836     */
837    public static function is_firefox_desktop( $user_agent = null ) {
838        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
839        if ( empty( $user_agent ) ) {
840            return false;
841        }
842
843        $ua = strtolower( wp_unslash( $user_agent ) );
844
845        if ( false !== strpos( $ua, 'firefox' ) && false === strpos( $ua, 'mobile' ) && false === strpos( $ua, 'tablet' ) ) {
846            return true;
847        } else {
848            return false;
849        }
850    }
851
852    /**
853     * Detects if the current browser is FirefoxOS Native browser
854     *
855     * Mozilla/5.0 (Mobile; rv:14.0) Gecko/14.0 Firefox/14.0
856     *
857     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
858     */
859    public static function is_firefox_os( $user_agent = null ) {
860        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
861        if ( empty( $user_agent ) ) {
862            return false;
863        }
864
865        $ua = strtolower( wp_unslash( $user_agent ) );
866
867        if ( strpos( $ua, 'mozilla' ) !== false && strpos( $ua, 'mobile' ) !== false && strpos( $ua, 'gecko' ) !== false && strpos( $ua, 'firefox' ) !== false ) {
868            return true;
869        } else {
870            return false;
871        }
872    }
873
874    /**
875     * Detect Safari browser
876     *
877     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
878     */
879    public static function is_safari_browser( $user_agent = null ) {
880        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
881        if ( empty( $user_agent ) ) {
882            return false;
883        }
884
885        if ( false === strpos( wp_unslash( $user_agent ), 'Safari' ) ) {
886            return false;
887        }
888        return true;
889    }
890
891    /**
892     * Detect Edge browser
893     *
894     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
895     */
896    public static function is_edge_browser( $user_agent = null ) {
897        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
898        if ( empty( $user_agent ) ) {
899            return false;
900        }
901
902        $ua = wp_unslash( $user_agent );
903
904        // Check for both legacy Edge ("Edge/") and modern Chromium-based Edge ("Edg/")
905        if ( false === strpos( $ua, 'Edge' ) && false === strpos( $ua, 'Edg/' ) ) {
906            return false;
907        }
908        return true;
909    }
910
911    /**
912     * Detect IE browser
913     *
914     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
915     */
916    public static function is_ie_browser( $user_agent = null ) {
917        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
918        if ( empty( $user_agent ) ) {
919            return false;
920        }
921
922        $ua = wp_unslash( $user_agent );
923        return strpos( $ua, 'MSIE' ) !== false || strpos( $ua, 'Trident/7' ) !== false;
924    }
925
926    /**
927     * Detect modern Opera desktop
928     *
929     * Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 OPR/74.0.3911.203
930     *
931     * Looking for "OPR/" specifically.
932     *
933     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
934     */
935    public static function is_opera_desktop( $user_agent = null ) {
936        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
937        if ( empty( $user_agent ) ) {
938            return false;
939        }
940
941        if ( false === strpos( wp_unslash( $user_agent ), 'OPR/' ) ) {
942            return false;
943        }
944
945        return true;
946    }
947
948    /**
949     * Detects if the current browser is Opera Mobile
950     *
951     * What is the difference between Opera Mobile and Opera Mini?
952     * - Opera Mobile is a full Internet browser for mobile devices.
953     * - Opera Mini always uses a transcoder to convert the page for a small display.
954     * (it uses Opera advanced server compression technology to compress web content before it gets to a device.
955     *  The rendering engine is on Opera's server.)
956     *
957     * Opera/9.80 (Windows NT 6.1; Opera Mobi/14316; U; en) Presto/2.7.81 Version/11.00"
958     * Opera/9.50 (Nintendo DSi; Opera/507; U; en-US)
959     *
960     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
961     */
962    public static function is_opera_mobile( $user_agent = null ) {
963        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
964        if ( empty( $user_agent ) ) {
965            return false;
966        }
967
968        $ua = strtolower( wp_unslash( $user_agent ) );
969
970        if ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'mobi' ) !== false ) {
971            return true;
972        } elseif ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'nintendo dsi' ) !== false ) {
973            return true;
974        } else {
975            return false;
976        }
977    }
978
979    /**
980     * Detects if the current browser is Opera Mini
981     *
982     * Opera/8.01 (J2ME/MIDP; Opera Mini/3.0.6306/1528; en; U; ssr)
983     * Opera/9.80 (Android;Opera Mini/6.0.24212/24.746 U;en) Presto/2.5.25 Version/10.5454
984     * Opera/9.80 (iPhone; Opera Mini/5.0.019802/18.738; U; en) Presto/2.4.15
985     * Opera/9.80 (J2ME/iPhone;Opera Mini/5.0.019802/886; U; ja) Presto/2.4.15
986     * Opera/9.80 (J2ME/iPhone;Opera Mini/5.0.019802/886; U; ja) Presto/2.4.15
987     * Opera/9.80 (Series 60; Opera Mini/5.1.22783/23.334; U; en) Presto/2.5.25 Version/10.54
988     * Opera/9.80 (BlackBerry; Opera Mini/5.1.22303/22.387; U; en) Presto/2.5.25 Version/10.54
989     *
990     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
991     */
992    public static function is_opera_mini( $user_agent = null ) {
993        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
994        if ( empty( $user_agent ) ) {
995            return false;
996        }
997
998        $ua = strtolower( wp_unslash( $user_agent ) );
999
1000        if ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'mini' ) !== false ) {
1001            return true;
1002        } else {
1003            return false;
1004        }
1005    }
1006
1007    /**
1008     * Detects if the current browser is Opera Mini, but not on a smart device OS(Android, iOS, etc)
1009     * Used to send users on dumb devices to m.wor
1010     *
1011     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1012     */
1013    public static function is_opera_mini_dumb( $user_agent = null ) {
1014        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1015        if ( empty( $user_agent ) ) {
1016            return false;
1017        }
1018
1019        $ua = strtolower( wp_unslash( $user_agent ) );
1020
1021        if ( self::is_opera_mini( $user_agent ) ) {
1022            if ( strpos( $ua, 'android' ) !== false || strpos( $ua, 'iphone' ) !== false || strpos( $ua, 'ipod' ) !== false
1023            || strpos( $ua, 'ipad' ) !== false || strpos( $ua, 'blackberry' ) !== false ) {
1024                return false;
1025            } else {
1026                return true;
1027            }
1028        } else {
1029            return false;
1030        }
1031    }
1032
1033    /**
1034     * Detects if the current browser is Samsung Internet for Android.
1035     *
1036     * Samsung Internet is the default browser on Samsung devices.
1037     * User agent contains: SamsungBrowser
1038     *
1039     * @param string|null $user_agent Optional user agent string.
1040     * @return bool
1041     */
1042    public static function is_samsung_browser( $user_agent = null ) {
1043        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1044        if ( empty( $user_agent ) ) {
1045            return false;
1046        }
1047
1048        return false !== stripos( $user_agent, 'SamsungBrowser' );
1049    }
1050
1051    /**
1052     * Detects if the current browser is UC Browser.
1053     *
1054     * UC Browser is popular in Asia and emerging markets.
1055     * User agent contains: UCBrowser or UCWEB
1056     *
1057     * @param string|null $user_agent Optional user agent string.
1058     * @return bool
1059     */
1060    public static function is_uc_browser( $user_agent = null ) {
1061        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1062        if ( empty( $user_agent ) ) {
1063            return false;
1064        }
1065
1066        return false !== stripos( $user_agent, 'UCBrowser' ) || false !== stripos( $user_agent, 'UCWEB' );
1067    }
1068
1069    /**
1070     * Detects if the current browser is Yandex Browser.
1071     *
1072     * Yandex Browser is popular in Russia and CIS countries.
1073     * User agent contains: YaBrowser
1074     *
1075     * @param string|null $user_agent Optional user agent string.
1076     * @return bool
1077     */
1078    public static function is_yandex_browser( $user_agent = null ) {
1079        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1080        if ( empty( $user_agent ) ) {
1081            return false;
1082        }
1083
1084        return false !== stripos( $user_agent, 'YaBrowser' );
1085    }
1086
1087    /**
1088     * Detects if the current browser is Vivaldi.
1089     *
1090     * Vivaldi is a feature-rich browser for power users.
1091     * User agent contains: Vivaldi
1092     *
1093     * @param string|null $user_agent Optional user agent string.
1094     * @return bool
1095     */
1096    public static function is_vivaldi_browser( $user_agent = null ) {
1097        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1098        if ( empty( $user_agent ) ) {
1099            return false;
1100        }
1101
1102        return false !== stripos( $user_agent, 'Vivaldi' );
1103    }
1104
1105    /**
1106     * Detects if the current browser is MIUI Browser.
1107     *
1108     * MIUI Browser is the default browser on Xiaomi devices.
1109     * User agent contains: MiuiBrowser or XiaoMi
1110     *
1111     * @param string|null $user_agent Optional user agent string.
1112     * @return bool
1113     */
1114    public static function is_miui_browser( $user_agent = null ) {
1115        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1116        if ( empty( $user_agent ) ) {
1117            return false;
1118        }
1119
1120        return false !== stripos( $user_agent, 'MiuiBrowser' ) || false !== stripos( $user_agent, 'XiaoMi' );
1121    }
1122
1123    /**
1124     * Detects if the current browser is Amazon Silk.
1125     *
1126     * Amazon Silk is the browser on Kindle Fire and Echo devices.
1127     * User agent contains: Silk or Silk-Accelerated
1128     *
1129     * @param string|null $user_agent Optional user agent string.
1130     * @return bool
1131     */
1132    public static function is_silk_browser( $user_agent = null ) {
1133        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1134        if ( empty( $user_agent ) ) {
1135            return false;
1136        }
1137
1138        return false !== stripos( $user_agent, 'Silk' );
1139    }
1140
1141    /**
1142     * Detects if the current browser is a Windows Phone 7 device.
1143     * ex: Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; LG; GW910)
1144     *
1145     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1146     */
1147    public static function is_WindowsPhone7( $user_agent = null ) {
1148        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1149        if ( empty( $user_agent ) ) {
1150            return false;
1151        }
1152
1153        $ua = strtolower( wp_unslash( $user_agent ) );
1154
1155        if ( false === strpos( $ua, 'windows phone os 7' ) ) {
1156            return false;
1157        } elseif ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1158            return false;
1159        } else {
1160            return true;
1161        }
1162    }
1163
1164    /**
1165     * Detects if the current browser is a Windows Phone 8 device.
1166     * ex: Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; ARM; Touch; IEMobile/10.0; <Manufacturer>; <Device> [;<Operator>])
1167     *
1168     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1169     */
1170    public static function is_windows_phone_8( $user_agent = null ) {
1171        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1172        if ( empty( $user_agent ) ) {
1173            return false;
1174        }
1175
1176        $ua = strtolower( wp_unslash( $user_agent ) );
1177        if ( strpos( $ua, 'windows phone 8' ) === false ) {
1178            return false;
1179        } else {
1180            return true;
1181        }
1182    }
1183
1184    /**
1185     * Detects if the current browser is on a Palm device running the new WebOS. This EXCLUDES TouchPad.
1186     *
1187     * Ex1: Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.1
1188     * Ex2: Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pixi/1.1
1189     *
1190     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1191     */
1192    public static function is_PalmWebOS( $user_agent = null ) {
1193        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1194        if ( empty( $user_agent ) ) {
1195            return false;
1196        }
1197
1198        $ua = strtolower( wp_unslash( $user_agent ) );
1199
1200        if ( false === strpos( $ua, 'webos' ) ) {
1201            return false;
1202        } elseif ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1203            return false;
1204        } else {
1205            return true;
1206        }
1207    }
1208
1209    /**
1210     * Detects if the current browser is the HP TouchPad default browser. This excludes phones wt WebOS.
1211     *
1212     * TouchPad Emulator: Mozilla/5.0 (hp-desktop; Linux; hpwOS/2.0; U; it-IT) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 Desktop/1.0
1213     * TouchPad: Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0
1214     *
1215     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1216     */
1217    public static function is_TouchPad( $user_agent = null ) {
1218        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1219        if ( empty( $user_agent ) ) {
1220            return false;
1221        }
1222
1223        $http_user_agent = strtolower( wp_unslash( $user_agent ) );
1224        if ( false !== strpos( $http_user_agent, 'hp-tablet' ) || false !== strpos( $http_user_agent, 'hpwos' ) || false !== strpos( $http_user_agent, 'touchpad' ) ) {
1225            if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1226                return false;
1227            } else {
1228                return true;
1229            }
1230        } else {
1231            return false;
1232        }
1233    }
1234
1235    /**
1236     * Detects if the current browser is the Series 60 Open Source Browser.
1237     *
1238     * OSS Browser 3.2 on E75: Mozilla/5.0 (SymbianOS/9.3; U; Series60/3.2 NokiaE75-1/110.48.125 Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413
1239     *
1240     * 7.0 Browser (Nokia 5800 XpressMusic (v21.0.025)) : Mozilla/5.0 (SymbianOS/9.4; U; Series60/5.0 Nokia5800d-1/21.0.025; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413
1241     *
1242     * Browser 7.1 (Nokia N97 (v12.0.024)) : Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/12.0.024; Profile/MIDP-2.1 Configuration/CLDC-1.1; en-us) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.12344
1243     *
1244     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1245     */
1246    public static function is_S60_OSSBrowser( $user_agent = null ) {
1247        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1248        if ( empty( $user_agent ) ) {
1249            return false;
1250        }
1251
1252        $agent = strtolower( wp_unslash( $user_agent ) );
1253        if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1254            return false;
1255        }
1256
1257        $pos_webkit = strpos( $agent, 'webkit' );
1258        if ( false !== $pos_webkit ) {
1259            // First, test for WebKit, then make sure it's either Symbian or S60.
1260            if ( strpos( $agent, 'symbian' ) !== false || strpos( $agent, 'series60' ) !== false ) {
1261                    return true;
1262            } else {
1263                return false;
1264            }
1265        } elseif ( strpos( $agent, 'symbianos' ) !== false && strpos( $agent, 'series60' ) !== false ) {
1266            return true;
1267        } elseif ( strpos( $agent, 'nokia' ) !== false && strpos( $agent, 'series60' ) !== false ) {
1268            return true;
1269        }
1270
1271        return false;
1272    }
1273
1274    /**
1275     * Detects if the device platform is the Symbian Series 60.
1276     *
1277     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1278     */
1279    public static function is_symbian_platform( $user_agent = null ) {
1280        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1281        if ( empty( $user_agent ) ) {
1282            return false;
1283        }
1284
1285        $agent = strtolower( wp_unslash( $user_agent ) );
1286
1287        $pos_webkit = strpos( $agent, 'webkit' );
1288        if ( false !== $pos_webkit ) {
1289            // First, test for WebKit, then make sure it's either Symbian or S60.
1290            if ( strpos( $agent, 'symbian' ) !== false || strpos( $agent, 'series60' ) !== false ) {
1291                    return true;
1292            } else {
1293                return false;
1294            }
1295        } elseif ( strpos( $agent, 'symbianos' ) !== false && strpos( $agent, 'series60' ) !== false ) {
1296            return true;
1297        } elseif ( strpos( $agent, 'nokia' ) !== false && strpos( $agent, 'series60' ) !== false ) {
1298            return true;
1299        } elseif ( strpos( $agent, 'opera mini' ) !== false ) {
1300            if ( strpos( $agent, 'symbianos' ) !== false || strpos( $agent, 'symbos' ) !== false || strpos( $agent, 'series 60' ) !== false ) {
1301                return true;
1302            }
1303        }
1304
1305        return false;
1306    }
1307
1308    /**
1309     * Detects if the device platform is the Symbian Series 40.
1310     * Nokia Browser for Series 40 is a proxy based browser, previously known as Ovi Browser.
1311     * This browser will report 'NokiaBrowser' in the header, however some older version will also report 'OviBrowser'.
1312     *
1313     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1314     */
1315    public static function is_symbian_s40_platform( $user_agent = null ) {
1316        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1317        if ( empty( $user_agent ) ) {
1318            return false;
1319        }
1320
1321        $agent = strtolower( wp_unslash( $user_agent ) );
1322
1323        if ( strpos( $agent, 'series40' ) !== false ) {
1324            if ( strpos( $agent, 'nokia' ) !== false || strpos( $agent, 'ovibrowser' ) !== false || strpos( $agent, 'nokiabrowser' ) !== false ) {
1325                return true;
1326            }
1327        }
1328
1329        return false;
1330    }
1331
1332    /**
1333     * Returns if the device belongs to J2ME capable family.
1334     *
1335     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1336     *
1337     * @return bool
1338     */
1339    public static function is_J2ME_platform( $user_agent = null ) {
1340        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1341        if ( empty( $user_agent ) ) {
1342            return false;
1343        }
1344
1345        $agent = strtolower( wp_unslash( $user_agent ) );
1346
1347        if ( strpos( $agent, 'j2me/midp' ) !== false ) {
1348            return true;
1349        } elseif ( strpos( $agent, 'midp' ) !== false && strpos( $agent, 'cldc' ) ) {
1350            return true;
1351        }
1352        return false;
1353    }
1354
1355    /**
1356     * Detects if the current UA is on one of the Maemo-based Nokia Internet Tablets.
1357     *
1358     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1359     */
1360    public static function is_MaemoTablet( $user_agent = null ) {
1361        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1362        if ( empty( $user_agent ) ) {
1363            return false;
1364        }
1365
1366        $agent = strtolower( wp_unslash( $user_agent ) );
1367
1368        $pos_maemo = strpos( $agent, 'maemo' );
1369        if ( false === $pos_maemo ) {
1370            return false;
1371        }
1372
1373        // Must be Linux + Tablet, or else it could be something else.
1374        if ( strpos( $agent, 'tablet' ) !== false && strpos( $agent, 'linux' ) !== false ) {
1375            if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1376                return false;
1377            } else {
1378                return true;
1379            }
1380        } else {
1381            return false;
1382        }
1383    }
1384
1385    /**
1386     * Detects if the current UA is a MeeGo device (Nokia Smartphone).
1387     *
1388     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1389     */
1390    public static function is_MeeGo( $user_agent = null ) {
1391        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1392        if ( empty( $user_agent ) ) {
1393            return false;
1394        }
1395
1396        $ua = strtolower( wp_unslash( $user_agent ) );
1397
1398        if ( false === strpos( $ua, 'meego' ) ) {
1399            return false;
1400        } elseif ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1401            return false;
1402        } else {
1403            return true;
1404        }
1405    }
1406
1407    /**
1408     * The is_webkit() method can be used to check the User Agent for an webkit generic browser.
1409     *
1410     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1411     */
1412    public static function is_webkit( $user_agent = null ) {
1413        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1414        if ( empty( $user_agent ) ) {
1415            return false;
1416        }
1417
1418        $agent = strtolower( wp_unslash( $user_agent ) );
1419
1420        $pos_webkit = strpos( $agent, 'webkit' );
1421
1422        if ( false !== $pos_webkit ) {
1423            return true;
1424        } else {
1425            return false;
1426        }
1427    }
1428
1429    /**
1430     * Detects if the current browser is the Native Android browser.
1431     *
1432     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1433     *
1434     * @return boolean true if the browser is Android otherwise false
1435     */
1436    public static function is_android( $user_agent = null ) {
1437        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1438        if ( empty( $user_agent ) ) {
1439            return false;
1440        }
1441
1442        $agent       = strtolower( wp_unslash( $user_agent ) );
1443        $pos_android = strpos( $agent, 'android' );
1444        if ( false !== $pos_android ) {
1445            if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1446                return false;
1447            } else {
1448                return true;
1449            }
1450        } else {
1451            return false;
1452        }
1453    }
1454
1455    /**
1456     * Detects if the current browser is the Native Android Tablet browser.
1457     * Assumes 'Android' should be in the user agent, but not 'mobile'
1458     *
1459     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1460     *
1461     * @return boolean true if the browser is Android and not 'mobile' otherwise false
1462     */
1463    public static function is_android_tablet( $user_agent = null ) {
1464        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1465        if ( empty( $user_agent ) ) {
1466            return false;
1467        }
1468
1469        $agent = strtolower( wp_unslash( $user_agent ) );
1470
1471        $pos_android      = strpos( $agent, 'android' );
1472        $pos_mobile       = strpos( $agent, 'mobile' );
1473        $post_android_app = strpos( $agent, 'wp-android' );
1474
1475        if ( false !== $pos_android && false === $pos_mobile && false === $post_android_app ) {
1476            if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1477                return false;
1478            } else {
1479                return true;
1480            }
1481        } else {
1482            return false;
1483        }
1484    }
1485
1486    /**
1487     * Detects if the current browser is the Kindle Fire Native browser.
1488     *
1489     * Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-84) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true
1490     * Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-84) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=false
1491     *
1492     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1493     *
1494     * @return boolean true if the browser is Kindle Fire Native browser otherwise false
1495     */
1496    public static function is_kindle_fire( $user_agent = null ) {
1497        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1498        if ( empty( $user_agent ) ) {
1499            return false;
1500        }
1501
1502        $agent        = strtolower( wp_unslash( $user_agent ) );
1503        $pos_silk     = strpos( $agent, 'silk/' );
1504        $pos_silk_acc = strpos( $agent, 'silk-accelerated=' );
1505        if ( false !== $pos_silk && false !== $pos_silk_acc ) {
1506            return true;
1507        } else {
1508            return false;
1509        }
1510    }
1511
1512    /**
1513     * Detects if the current browser is the Kindle Touch Native browser
1514     *
1515     * Mozilla/5.0 (X11; U; Linux armv7l like Android; en-us) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/533.2+ Kindle/3.0+
1516     *
1517     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1518     *
1519     * @return boolean true if the browser is Kindle monochrome Native browser otherwise false
1520     */
1521    public static function is_kindle_touch( $user_agent = null ) {
1522        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1523        if ( empty( $user_agent ) ) {
1524            return false;
1525        }
1526
1527        $agent            = strtolower( wp_unslash( $user_agent ) );
1528        $pos_kindle_touch = strpos( $agent, 'kindle/3.0+' );
1529        if ( false !== $pos_kindle_touch && false === self::is_kindle_fire( $user_agent ) ) {
1530            return true;
1531        } else {
1532            return false;
1533        }
1534    }
1535
1536    /**
1537     * Detect if user agent is the WordPress.com Windows 8 app (used ONLY on the custom oauth stylesheet)
1538     *
1539     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1540     */
1541    public static function is_windows8_auth( $user_agent = null ) {
1542        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1543        if ( empty( $user_agent ) ) {
1544            return false;
1545        }
1546
1547        $agent = strtolower( wp_unslash( $user_agent ) );
1548        $pos   = strpos( $agent, 'msauthhost' );
1549        if ( false !== $pos ) {
1550            return true;
1551        } else {
1552            return false;
1553        }
1554    }
1555
1556    /**
1557     * Detect if user agent is the WordPress.com Windows 8 app.
1558     *
1559     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1560     */
1561    public static function is_wordpress_for_win8( $user_agent = null ) {
1562        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1563        if ( empty( $user_agent ) ) {
1564            return false;
1565        }
1566
1567        $agent = strtolower( wp_unslash( $user_agent ) );
1568        $pos   = strpos( $agent, 'wp-windows8' );
1569        if ( false !== $pos ) {
1570            return true;
1571        } else {
1572            return false;
1573        }
1574    }
1575
1576    /**
1577     * Detect if user agent is the WordPress.com Desktop app.
1578     *
1579     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1580     */
1581    public static function is_wordpress_desktop_app( $user_agent = null ) {
1582        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1583        if ( empty( $user_agent ) ) {
1584            return false;
1585        }
1586
1587        $agent = strtolower( wp_unslash( $user_agent ) );
1588        $pos   = strpos( $agent, 'WordPressDesktop' );
1589        if ( false !== $pos ) {
1590            return true;
1591        } else {
1592            return false;
1593        }
1594    }
1595
1596    /**
1597     * The is_blackberry_tablet() method can be used to check the User Agent for a RIM blackberry tablet.
1598     * The user agent of the BlackBerry® Tablet OS follows a format similar to the following:
1599     * Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/0.0.1 Safari/534.8+
1600     *
1601     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1602     */
1603    public static function is_blackberry_tablet( $user_agent = null ) {
1604        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1605        if ( empty( $user_agent ) ) {
1606            return false;
1607        }
1608
1609        $agent          = strtolower( wp_unslash( $user_agent ) );
1610        $pos_playbook   = stripos( $agent, 'PlayBook' );
1611        $pos_rim_tablet = stripos( $agent, 'RIM Tablet' );
1612
1613        if ( ( false === $pos_playbook ) || ( false === $pos_rim_tablet ) ) {
1614            return false;
1615        } else {
1616            return true;
1617        }
1618    }
1619
1620    /**
1621     * The is_blackbeberry() method can be used to check the User Agent for a blackberry device.
1622     * Note that opera mini on BB matches this rule.
1623     *
1624     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1625     */
1626    public static function is_blackbeberry( $user_agent = null ) {
1627        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1628        if ( empty( $user_agent ) ) {
1629            return false;
1630        }
1631
1632        $agent = strtolower( wp_unslash( $user_agent ) );
1633
1634        $pos_blackberry = strpos( $agent, 'blackberry' );
1635        if ( false !== $pos_blackberry ) {
1636            if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1637                return false;
1638            } else {
1639                return true;
1640            }
1641        } else {
1642            return false;
1643        }
1644    }
1645
1646    /**
1647     * The is_blackberry_10() method can be used to check the User Agent for a BlackBerry 10 device.
1648     *
1649     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1650     */
1651    public static function is_blackberry_10( $user_agent = null ) {
1652        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1653        if ( empty( $user_agent ) ) {
1654            return false;
1655        }
1656
1657        $agent = strtolower( wp_unslash( $user_agent ) );
1658        return ( strpos( $agent, 'bb10' ) !== false ) && ( strpos( $agent, 'mobile' ) !== false );
1659    }
1660
1661    /**
1662     * Determines whether a desktop platform is Linux OS
1663     *
1664     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1665     *
1666     * @return bool
1667     */
1668    public static function is_linux_desktop( $user_agent = null ) {
1669        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1670        if ( empty( $user_agent ) ) {
1671            return false;
1672        }
1673
1674        if ( ! preg_match( '/linux/i', wp_unslash( $user_agent ) ) ) {
1675            return false;
1676        }
1677        return true;
1678    }
1679
1680    /**
1681     * Determines whether a desktop platform is Mac OS
1682     *
1683     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1684     *
1685     * @return bool
1686     */
1687    public static function is_mac_desktop( $user_agent = null ) {
1688        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1689        if ( empty( $user_agent ) ) {
1690            return false;
1691        }
1692
1693        if ( ! preg_match( '/macintosh|mac os x/i', wp_unslash( $user_agent ) ) ) {
1694            return false;
1695        }
1696        return true;
1697    }
1698
1699    /**
1700     * Determines whether a desktop platform is Windows OS
1701     *
1702     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1703     *
1704     * @return bool
1705     */
1706    public static function is_windows_desktop( $user_agent = null ) {
1707        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1708        if ( empty( $user_agent ) ) {
1709            return false;
1710        }
1711
1712        if ( ! preg_match( '/windows|win32/i', wp_unslash( $user_agent ) ) ) {
1713            return false;
1714        }
1715        return true;
1716    }
1717
1718    /**
1719     * Determines whether a desktop platform is Chrome OS
1720     *
1721     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1722     *
1723     * @return bool
1724     */
1725    public static function is_chrome_desktop( $user_agent = null ) {
1726        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1727        if ( empty( $user_agent ) ) {
1728            return false;
1729        }
1730
1731        if ( ! preg_match( '/chrome/i', wp_unslash( $user_agent ) ) ) {
1732            return false;
1733        }
1734        return true;
1735    }
1736
1737    /**
1738     * Retrieve the blackberry OS version.
1739     *
1740     * Return strings are from the following list:
1741     * - blackberry-10
1742     * - blackberry-7
1743     * - blackberry-6
1744     * - blackberry-torch //only the first edition. The 2nd edition has the OS7 onboard and doesn't need any special rule.
1745     * - blackberry-5
1746     * - blackberry-4.7
1747     * - blackberry-4.6
1748     * - blackberry-4.5
1749     *
1750     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1751     *
1752     * @return string Version of the BB OS.
1753     * If version is not found, get_blackbeberry_OS_version will return boolean false.
1754     */
1755    public static function get_blackbeberry_OS_version( $user_agent = null ) {
1756        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1757        if ( empty( $user_agent ) ) {
1758            return false;
1759        }
1760
1761        if ( self::is_blackberry_10( $user_agent ) ) {
1762            return 'blackberry-10';
1763        }
1764
1765        $agent = strtolower( wp_unslash( $user_agent ) );
1766
1767        $pos_blackberry = stripos( $agent, 'blackberry' );
1768        if ( false === $pos_blackberry ) {
1769            // Not a blackberry device.
1770            return false;
1771        }
1772
1773        // Blackberry devices OS 6.0 or higher.
1774        // Mozilla/5.0 (BlackBerry; U; BlackBerry 9670; en) AppleWebKit/534.3+ (KHTML, like Gecko) Version/6.0.0.286 Mobile Safari/534.3+.
1775        // Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, Like Gecko) Version/6.0.0.141 Mobile Safari/534.1+.
1776        // Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en-US) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.0.0 Mobile Safari/534.11+.
1777        $pos_webkit = stripos( $agent, 'webkit' );
1778        if ( false !== $pos_webkit ) {
1779            // Detected blackberry webkit browser.
1780            $pos_torch = stripos( $agent, 'BlackBerry 9800' );
1781            if ( false !== $pos_torch ) {
1782                return 'blackberry-torch'; // Match the torch first edition. the 2nd edition should use the OS7 and doesn't need any special rule.
1783            } elseif ( preg_match( '#Version\/([\d\.]+)#i', $agent, $matches ) ) { // Detecting the BB OS version for devices running OS 6.0 or higher.
1784                $version     = $matches[1];
1785                $version_num = explode( '.', $version );
1786                if ( count( $version_num ) <= 1 ) {
1787                    return 'blackberry-6'; // not a BB device that match our rule.
1788                } else {
1789                    return 'blackberry-' . $version_num[0];
1790                }
1791            } else {
1792                // if doesn't match returns the minimun version with a webkit browser. we should never fall here.
1793                return 'blackberry-6'; // not a BB device that match our rule.
1794            }
1795        }
1796
1797        // Blackberry devices <= 5.XX.
1798        // BlackBerry9000/5.0.0.93 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/179.
1799        if ( preg_match( '#BlackBerry\w+\/([\d\.]+)#i', $agent, $matches ) ) {
1800            $version = $matches[1];
1801        } else {
1802            return false; // not a BB device that match our rule.
1803        }
1804
1805        $version_num = explode( '.', $version );
1806
1807        if ( count( $version_num ) <= 1 ) {
1808            return false;
1809        }
1810
1811        $version_num_major = (int) $version_num[0];
1812        $version_num_minor = (int) $version_num[1];
1813
1814        if ( 5 === $version_num_major ) {
1815            return 'blackberry-5';
1816        } elseif ( 4 === $version_num_major && 7 === $version_num_minor ) {
1817            return 'blackberry-4.7';
1818        } elseif ( 4 === $version_num_major && 6 === $version_num_minor ) {
1819            return 'blackberry-4.6';
1820        } elseif ( 4 === $version_num_major && 5 === $version_num_minor ) {
1821            return 'blackberry-4.5';
1822        } else {
1823            return false;
1824        }
1825    }
1826
1827    /**
1828     * Retrieve the blackberry browser version.
1829     *
1830     * Return string are from the following list:
1831     * - blackberry-10
1832     * - blackberry-webkit
1833     * - blackberry-5
1834     * - blackberry-4.7
1835     * - blackberry-4.6
1836     *
1837     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1838     *
1839     * @return string Type of the BB browser.
1840     * If browser's version is not found, detect_blackbeberry_browser_version will return boolean false.
1841     */
1842    public static function detect_blackberry_browser_version( $user_agent = null ) {
1843        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1844        if ( empty( $user_agent ) ) {
1845            return false;
1846        }
1847
1848        $agent = strtolower( wp_unslash( $user_agent ) );
1849
1850        if ( self::is_blackberry_10( $user_agent ) ) {
1851            return 'blackberry-10';
1852        }
1853
1854        $pos_blackberry = strpos( $agent, 'blackberry' );
1855        if ( false === $pos_blackberry ) {
1856            // Not a blackberry device.
1857            return false;
1858        }
1859
1860        $pos_webkit = strpos( $agent, 'webkit' );
1861
1862        if ( ! ( false === $pos_webkit ) ) {
1863            return 'blackberry-webkit';
1864        } else {
1865            if ( ! preg_match( '#BlackBerry\w+\/([\d\.]+)#i', $agent, $matches ) ) {
1866                return false; // not a BB device that match our rule.
1867            }
1868
1869            $version_num = explode( '.', $matches[1] );
1870
1871            if ( count( $version_num ) <= 1 ) {
1872                return false;
1873            }
1874
1875            $version_num_major = (int) $version_num[0];
1876            $version_num_minor = (int) $version_num[1];
1877
1878            if ( 5 === $version_num_major ) {
1879                return 'blackberry-5';
1880            } elseif ( 4 === $version_num_major && 7 === $version_num_minor ) {
1881                return 'blackberry-4.7';
1882            } elseif ( 4 === $version_num_major && 6 === $version_num_minor ) {
1883                return 'blackberry-4.6';
1884            } else {
1885                // A very old BB device is found or this is a BB device that doesn't match our rules.
1886                return false;
1887            }
1888        }
1889    }
1890
1891    /**
1892     * Checks if a visitor is coming from one of the WordPress mobile apps.
1893     *
1894     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1895     *
1896     * @return bool
1897     */
1898    public static function is_mobile_app( $user_agent = null ) {
1899        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1900        if ( empty( $user_agent ) ) {
1901            return false;
1902        }
1903
1904        $agent = strtolower( wp_unslash( $user_agent ) );
1905
1906        if ( isset( $_SERVER['X_USER_AGENT'] ) && preg_match( '|wp-webos|', $_SERVER['X_USER_AGENT'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
1907            return true; // Wp4webos 1.1 or higher.
1908        }
1909
1910        $app_agents = array( 'wp-android', 'wp-blackberry', 'wp-iphone', 'wp-nokia', 'wp-webos', 'wp-windowsphone' );
1911        // the mobile reader on iOS has an incorrect UA when loading the reader
1912        // currently it is the default one provided by the iOS framework which
1913        // causes problems with 2-step-auth
1914        // User-Agent   WordPress/3.1.4 CFNetwork/609 Darwin/13.0.0.
1915        $app_agents[] = 'wordpress/3.1';
1916
1917        foreach ( $app_agents as $app_agent ) {
1918            if ( false !== strpos( $agent, $app_agent ) ) {
1919                return true;
1920            }
1921        }
1922        return false;
1923    }
1924
1925    /**
1926     * Detects if the current browser is Nintendo 3DS handheld.
1927     *
1928     * Example: Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7498.US
1929     * can differ in language, version and region
1930     *
1931     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1932     */
1933    public static function is_Nintendo_3DS( $user_agent = null ) {
1934        $user_agent = self::maybe_get_user_agent_from_server( $user_agent );
1935        if ( empty( $user_agent ) ) {
1936            return false;
1937        }
1938
1939        $ua = strtolower( wp_unslash( $user_agent ) );
1940        if ( strpos( $ua, 'nintendo 3ds' ) !== false ) {
1941            return true;
1942        }
1943        return false;
1944    }
1945
1946    /**
1947     * Was the current request made by a known bot?
1948     *
1949     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1950     *
1951     * @return boolean
1952     */
1953    public static function is_bot( $user_agent = null ) {
1954        static $is_bot = null;
1955
1956        if ( null === $user_agent ) {
1957            if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1958                return false;
1959            }
1960            $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1961
1962            // Use cached result only when using the default $_SERVER['HTTP_USER_AGENT'].
1963            if ( $is_bot === null ) {
1964                $is_bot = self::is_bot_user_agent( $user_agent );
1965            }
1966            return $is_bot;
1967        }
1968
1969        if ( empty( $user_agent ) ) {
1970            return false;
1971        }
1972
1973        // Don't use cache when a custom user agent is provided.
1974        return self::is_bot_user_agent( $user_agent );
1975    }
1976
1977    /**
1978     * Is the given user-agent a known bot?
1979     * If you want an is_bot check for the current request's UA, use is_bot() instead of passing a user-agent to this method.
1980     *
1981     * @param string $ua A user-agent string.
1982     *
1983     * @return boolean
1984     */
1985    public static function is_bot_user_agent( $ua = null ) {
1986
1987        if ( empty( $ua ) ) {
1988            return false;
1989        }
1990
1991        // Some sourced via
1992        // https://github.com/ua-parser/uap-core/blob/432e95f6767cc8bab4c20c255784cd6f7e93bc15/regexes.yaml#L151
1993        $bot_agents = array(
1994            // Microsoft/Bing https://www.bing.com/webmasters/help/which-crawlers-does-bing-use-8c184ec0
1995            'bingbot', // Bing/Copilot
1996            'adidxbot', // Bing Ads
1997            'bingpreview', // Generates page snapshots for Bing
1998            'bingvideopreview', // Generates previews of videos for Bing
1999            'microsoft',
2000
2001            // Google https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers
2002            'adsbot-google',
2003            'appengine-google',
2004            'feedfetcher-google',
2005            'mediapartners-google',
2006            'storebot-google', // https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers#google-storebot
2007            'google sketchup',
2008            'google-cloudbertexbot', // https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers#google-cloudvertexbot
2009            'google-extended', // Gemini https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers#google-extended
2010            'google-inspectiontool', // https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers
2011            'google-safety;', // https://www.google.com/bot.html
2012            'googlebot-mobile',
2013            'googlebot', // and googlebot-[image,video,news,] https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers#googlebot
2014            'googleother', // and googleother-[video,image] https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers#googleother
2015
2016            // OpenAI https://platform.openai.com/docs/bots
2017            'gptbot', // Crawler
2018            'chatgpt-user', // ChatGPT on behalf of user
2019            'oai-searchbot', // ChatGPT search features
2020
2021            // Anthropic
2022            'claudebot', // chat citation fetch https://support.anthropic.com/en/articles/8896518
2023            'claude-web', // web-focused crawl https://darkvisitors.com/agents/claude-web
2024            'anthropic-ai', // bulk model training https://darkvisitors.com/agents/anthropic-ai
2025
2026            // Perplexity
2027            'perplexitybot', // index builder https://docs.perplexity.ai/guides/bots
2028            'perplexity-user', // human-triggered visit https://docs.perplexity.ai/guides/bots
2029
2030            // Pinterest https://help.pinterest.com/en/business/article/pinterestbot
2031            'pinterest/0.2',
2032            'pinterestbot',
2033
2034            // Meta https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/
2035            'facebookbot', // AI data scraper https://darkvisitors.com/agents/facebookbot
2036            'facebookexternalhit', // shares https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/#identify
2037            'facebookcatalog', // shares https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/#identify
2038            'meta-webindexer', // Meta AI search indexer https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/#meta-webindexer
2039            'meta-externalads', // web crawler improving ads https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/#meta-externalads
2040            'meta-externalagent', // training AI models https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/#identify-2
2041            'meta-externalfetcher', // user-initiated fetches, may skip robots.txt https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/#identify-3
2042
2043            // Semrush https://www.semrush.com/bot/
2044            'semrushbot',
2045            'siteauditbot',
2046
2047            // Other bots (alphabetized list)
2048            '123metaspider-bot',
2049            '1470.net crawler',
2050            '50.nu',
2051            '8bo crawler bot',
2052            'aboundex',
2053            'ahrefsbot',
2054            'ai2bot', // AI2 crawler for LLMm training https://allenai.org/crawler
2055            'alexa',
2056            'altavista',
2057            'amazonbot', // https://developer.amazon.com/amazonbot
2058            'applebot', // https://support.apple.com/en-ca/HT204683
2059            'arcgis hub indexer',
2060            'archive.org_bot', // http://archive.org/details/archive.org_bot
2061            'archiver',
2062            'ask jeeves',
2063            'attentio',
2064            'baiduspider',
2065            'blexbot',
2066            'blitzbot',
2067            'blogbridge',
2068            'bloglovin',
2069            'bne.es_bot', // https://www.bne.es/es/colecciones/archivo-web-espanola/aviso-webmasters
2070            'boardreader blog indexer',
2071            'boardreader favicon fetcher',
2072            'boitho.com-dc',
2073            'botseer',
2074            'bubing',
2075            'bytespider', // ByteDance (owner of TikTok) to train LLMs for Doubao https://darkvisitors.com/agents/bytespider
2076            'catchpoint',
2077            'ccbot', // CommonCrawl non-profit https://commoncrawl.org/ccbot
2078            'charlotte',
2079            'checklinks',
2080            'chtml generic',
2081            'cityreview robot',
2082            'cloudflare-alwaysonline',
2083            'clumboot',
2084            'coccocbot', // Coc Coc https://darkvisitors.com/agents/coccocbot-web
2085            'cohere-ai', // Cohere AI https://darkvisitors.com/agents/cohere-ai
2086            'comodo http',
2087            'comodo-webinspector-crawler',
2088            'converacrawler',
2089            'cookieinformationscanner', // Internal ref p1699315886066389-slack-C0438NHCLSY
2090            'crawl-e',
2091            'crawlconvera',
2092            'crawldaddy',
2093            'crawler',
2094            'crawlfire',
2095            'csimplespider',
2096            'dataforseobot', // https://www.dataforseo.com/dataforseo-bot
2097            'daumoa',
2098            'diffbot', // https://docs.diffbot.com/docs/how-to-use-custom-user-agents-with-extract-apis & https://darkvisitors.com/agents/diffbot
2099            'domaintunocrawler',
2100            'dotbot', // https://darkvisitors.com/agents/dotbot
2101            'duckassistbot', // DuckDuckGo AI Assistant https://darkvisitors.com/agents/duckassistbot
2102            'elisabot',
2103            'ezlynxbot', // https://www.ezoic.com/bot
2104            'fastmobilecrawl',
2105            'feed seeker bot',
2106            'feedbin',
2107            'feedburner',
2108            'finderbots',
2109            'findlinks',
2110            'firefly',
2111            'flamingo_searchengine',
2112            'followsite bot',
2113            'froogle',
2114            'furlbot',
2115            'genieo',
2116            'germcrawler',
2117            'gigabot',
2118            'gomezagent',
2119            'gonzo1',
2120            'grapeshotcrawler',
2121            'grokkit-crawler',
2122            'grub-client',
2123            'gsa-crawler',
2124            'heritrix',
2125            'hiddenmarket',
2126            'holmes',
2127            'hoowwwer',
2128            'htdig',
2129            'httrack',
2130            'ia_archiver',
2131            'icarus6j',
2132            'icc-crawler',
2133            'ichiro',
2134            'iconsurf',
2135            'iescholar',
2136            'iltrovatore',
2137            'index crawler',
2138            'infoseek',
2139            'infuzapp',
2140            'innovazion crawler',
2141            'internetarchive',
2142            'irlbot',
2143            'jbot',
2144            'job roboter',
2145            'jumpbot',
2146            'kaloogabot',
2147            'kiwistatus spider',
2148            'kraken',
2149            'kurzor',
2150            'larbin',
2151            'leia',
2152            'lesnikbot',
2153            'lijit crawler',
2154            'linguee bot',
2155            'linkaider',
2156            'linkcheck',
2157            'linkdexbot',
2158            'linkedinbot',
2159            'linkfluence', // http://linkfluence.com/
2160            'linkwalker', // https://www.linkwalker.com/
2161            'lite bot',
2162            'livelapbot',
2163            'llaut',
2164            'lycos',
2165            'mail.ru_bot',
2166            'masidani_bot',
2167            'masscan',
2168            'mediapartners',
2169            'mediobot',
2170            'mj12bot',
2171            'mogimogi',
2172            'mojeekbot', // https://www.mojeek.com/bot.html
2173            'motionbot',
2174            'mozdex',
2175            'mshots',
2176            'msnbot',
2177            'msrbot',
2178            'mtps feed aggregation system',
2179            'netresearch',
2180            'netvibes',
2181            'newsgator',
2182            'ning',
2183            'nutch',
2184            'nymesis',
2185            'objectssearch',
2186            'ogscrper',
2187            'omgili', // Webz.io web crawler for a data seller https://darkvisitors.com/agents/omgili
2188            'oozbot',
2189            'openbot',
2190            'openhosebot',
2191            'orbiter',
2192            'pagepeeker',
2193            'pagesinventory',
2194            'paxleframework',
2195            'peeplo screenshot bot',
2196            'phpcrawl',
2197            'pingdom.com_bot',
2198            'plantynet_webrobot',
2199            'pompos',
2200            'pss-webkit-request',
2201            'pythumbnail',
2202            'queryseekersp ider',
2203            'queryseekerspider',
2204            'qwantify',
2205            'read%20later',
2206            'reaper',
2207            'redcarpet',
2208            'retreiver',
2209            'riddler',
2210            'rival iq',
2211            'scollspider',
2212            'scooter',
2213            'scrapy',
2214            'scrubby',
2215            'searchsight',
2216            'seekbot',
2217            'semanticdiscovery',
2218            'seostats',
2219            'simplepie',
2220            'simplerss',
2221            'simpy',
2222            'sitecat webbot',
2223            'sitecon',
2224            'slack-imgproxy',
2225            'slackbot-linkexpanding',
2226            'slurp',
2227            'snapbot',
2228            'snapchat', // https://developers.snap.com/robots
2229            'snappy',
2230            'speedy spider',
2231            'spider',
2232            'squrl java',
2233            'stringer',
2234            'taptubot',
2235            'technoratisnoop',
2236            'teoma',
2237            'theusefulbot',
2238            'thumbshots.ru',
2239            'thumbshotsbot',
2240            'timpibot', // LLM trainer https://darkvisitors.com/agents/timpibot
2241            'tiny tiny rss',
2242            'trendictionbot',  // http://www.trendiction.de/bot;
2243            'trends crawler',
2244            'tweetmemebot',
2245            'twiceler',
2246            'twitterbot', // https://developer.x.com/en/docs/x-for-websites/cards/guides/getting-started#crawling
2247            'url2png',
2248            'usyd-nlp-spider',
2249            'vagabondo',
2250            'voilabot',
2251            'vortex',
2252            'votay bot',
2253            'voyager',
2254            'wasalive.bot',
2255            'web-sniffer',
2256            'webthumb',
2257            'wesee',
2258            'whatsapp',
2259            'whatweb',
2260            'wire',
2261            'wordpress',
2262            'wotbox',
2263            'wp-e2e-tests', // WordPress e2e tests
2264            'www.almaden.ibm.com',
2265            'xenu',
2266            'yacybot', // http://yacy.net/bot.html
2267            'yahoo! slurp',
2268            'yahooseeker',
2269            'yahooysmcm',
2270            'yammybot',
2271            'yandexbot',
2272            'yottaamonitor',
2273            'youbot', // You.com AI assistant https://darkvisitors.com/agents/youbot
2274            'yowedo',
2275            'zao-crawler',
2276            'zao',
2277            'zebot_www.ze.bz',
2278            'zoombot', // SEOZOom https://darkvisitors.com/agents/zoombot
2279            'zooshot',
2280            'zyborg',
2281        );
2282
2283        foreach ( $bot_agents as $bot_agent ) {
2284            if ( false !== stripos( $ua, $bot_agent ) ) {
2285                return true;
2286            }
2287        }
2288
2289        return false;
2290    }
2291
2292    /**
2293     * Is the current request from an agent?
2294     *
2295     * Detects bots, crawlers, AI assistants, and programmatic HTTP clients.
2296     * Useful for showing machine-readable hints or alternative content to non-browser clients.
2297     *
2298     * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
2299     *
2300     * @return bool True if the request appears to be from an agent.
2301     */
2302    public static function is_agent( $user_agent = null ) {
2303        static $cached_ua     = null;
2304        static $cached_result = null;
2305
2306        if ( null === $user_agent ) {
2307            if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
2308                // Empty User-Agent is likely programmatic - real browsers always send one.
2309                return true;
2310            }
2311            $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
2312
2313            // Use cache for default user agent, but invalidate if UA changed.
2314            if ( $cached_ua === $user_agent && null !== $cached_result ) {
2315                return $cached_result;
2316            }
2317
2318            $cached_ua     = $user_agent;
2319            $cached_result = self::is_agent_user_agent( $user_agent );
2320
2321            /** This filter is documented below. */
2322            if ( function_exists( 'apply_filters' ) ) {
2323                $cached_result = apply_filters( 'jetpack_is_agent', $cached_result, $user_agent );
2324            }
2325            return $cached_result;
2326        }
2327
2328        if ( empty( $user_agent ) ) {
2329            // Empty User-Agent is likely programmatic.
2330            return true;
2331        }
2332
2333        $result = self::is_agent_user_agent( $user_agent );
2334
2335        /**
2336         * Filter to customize agent detection.
2337         *
2338         * @param bool   $is_agent   Whether the request is from an agent.
2339         * @param string $user_agent The user agent string.
2340         */
2341        if ( function_exists( 'apply_filters' ) ) {
2342            $result = apply_filters( 'jetpack_is_agent', $result, $user_agent );
2343        }
2344        return $result;
2345    }
2346
2347    /**
2348     * Is the given user-agent from an agent?
2349     *
2350     * @param string $ua A user-agent string.
2351     *
2352     * @return bool True if the user-agent appears to be from an agent.
2353     */
2354    private static function is_agent_user_agent( $ua ) {
2355        if ( self::is_bot_user_agent( $ua ) ) {
2356            return true;
2357        }
2358
2359        // HTTP libraries and programmatic clients (alphabetized).
2360        $http_clients = array(
2361            'aiohttp',
2362            'axios',
2363            'curl/',
2364            'go-http-client',
2365            'got/',
2366            'guzzlehttp',
2367            'httpie',
2368            'httpx',
2369            'insomnia',
2370            'java/',
2371            'libwww-perl',
2372            'node-fetch',
2373            'okhttp',
2374            'postman',
2375            'python-requests',
2376            'python-urllib',
2377            'ruby',
2378            'undici',
2379            'wget/',
2380        );
2381
2382        foreach ( $http_clients as $client ) {
2383            if ( false !== stripos( $ua, $client ) ) {
2384                return true;
2385            }
2386        }
2387
2388        return false;
2389    }
2390}