PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-device-detection/src/class-user-agent-info.php +1083 -265 12.7.3 → 16.3 View file →
@@ -4,8 +4,9 @@
4 4 *
5 5 * @package automattic/jetpack-device-detection
6 6 *
7 7 * We don't want to rename public members.
8 + *
8 9 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
9 10 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
10 11 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
11 12 * @phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
@@ -16,8 +17,11 @@
16 17 require_once __DIR__ . '/functions.php';
17 18
18 19 /**
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.
20 24 */
21 25 class User_Agent_Info {
22 26
23 27 /**
@@ -59,22 +63,55 @@
59 63 * Stores the device platform name
60 64 *
61 65 * @var null|string
62 66 */
63 - private $platform = null;
64 - const PLATFORM_WINDOWS = 'windows';
65 - const PLATFORM_IPHONE = 'iphone';
66 - const PLATFORM_IPOD = 'ipod';
67 - const PLATFORM_IPAD = 'ipad';
68 - const PLATFORM_BLACKBERRY = 'blackberry';
69 - const PLATFORM_BLACKBERRY_10 = 'blackberry_10';
70 - const PLATFORM_SYMBIAN = 'symbian_series60';
71 - const PLATFORM_SYMBIAN_S40 = 'symbian_series40';
72 - const PLATFORM_J2ME_MIDP = 'j2me_midp';
73 - const PLATFORM_ANDROID = 'android';
74 - const PLATFORM_ANDROID_TABLET = 'android_tablet';
75 - const PLATFORM_FIREFOX_OS = 'firefoxOS';
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';
76 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 +
77 114 /**
78 115 * A list of dumb-phone user agent parts.
79 116 *
80 117 * @var array
@@ -136,9 +173,9 @@
136 173 public function __construct( $ua = '' ) {
137 174 if ( $ua ) {
138 175 $this->useragent = $ua;
139 176 } elseif ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
140 - $this->useragent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This class is all about validating.
177 + $this->useragent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This class is all about validating.
141 178 }
142 179 }
143 180
144 181 /**
@@ -146,65 +183,65 @@
146 183 *
147 184 * @return string The matched User Agent name, false otherwise.
148 185 */
149 186 public function get_mobile_user_agent_name() {
150 - if ( $this->is_chrome_for_iOS() ) { // Keep this check before the safari rule.
187 + if ( static::is_chrome_for_iOS() ) { // Keep this check before the safari rule.
151 188 return 'chrome-for-ios';
152 - } elseif ( $this->is_iphone_or_ipod( 'iphone-safari' ) ) {
189 + } elseif ( static::is_iphone_or_ipod( 'iphone-safari' ) ) {
153 190 return 'iphone';
154 - } elseif ( $this->is_ipad( 'ipad-safari' ) ) {
191 + } elseif ( static::is_ipad( 'ipad-safari' ) ) {
155 192 return 'ipad';
156 - } elseif ( $this->is_android_tablet() ) { // Keep this check before the android rule.
193 + } elseif ( static::is_android_tablet() ) { // Keep this check before the android rule.
157 194 return 'android_tablet';
158 - } elseif ( $this->is_android() ) {
195 + } elseif ( static::is_android() ) {
159 196 return 'android';
160 - } elseif ( $this->is_blackberry_10() ) {
197 + } elseif ( static::is_blackberry_10() ) {
161 198 return 'blackberry_10';
162 - } elseif ( $this->is_blackbeberry() ) {
199 + } elseif ( static::is_blackbeberry() ) {
163 200 return 'blackberry';
164 - } elseif ( $this->is_WindowsPhone7() ) {
201 + } elseif ( static::is_WindowsPhone7() ) {
165 202 return 'win7';
166 - } elseif ( $this->is_windows_phone_8() ) {
203 + } elseif ( static::is_windows_phone_8() ) {
167 204 return 'winphone8';
168 - } elseif ( $this->is_opera_mini() ) {
205 + } elseif ( static::is_opera_mini() ) {
169 206 return 'opera-mini';
170 - } elseif ( $this->is_opera_mini_dumb() ) {
207 + } elseif ( static::is_opera_mini_dumb() ) {
171 208 return 'opera-mini-dumb';
172 - } elseif ( $this->is_opera_mobile() ) {
209 + } elseif ( static::is_opera_mobile() ) {
173 210 return 'opera-mobi';
174 - } elseif ( $this->is_blackberry_tablet() ) {
211 + } elseif ( static::is_blackberry_tablet() ) {
175 212 return 'blackberry_tablet';
176 - } elseif ( $this->is_kindle_fire() ) {
213 + } elseif ( static::is_kindle_fire() ) {
177 214 return 'kindle-fire';
178 - } elseif ( $this->is_PalmWebOS() ) {
215 + } elseif ( static::is_PalmWebOS() ) {
179 216 return 'webos';
180 - } elseif ( $this->is_S60_OSSBrowser() ) {
217 + } elseif ( static::is_S60_OSSBrowser() ) {
181 218 return 'series60';
182 - } elseif ( $this->is_firefox_os() ) {
219 + } elseif ( static::is_firefox_os() ) {
183 220 return 'firefoxOS';
184 - } elseif ( $this->is_firefox_mobile() ) {
221 + } elseif ( static::is_firefox_mobile() ) {
185 222 return 'firefox_mobile';
186 - } elseif ( $this->is_MaemoTablet() ) {
223 + } elseif ( static::is_MaemoTablet() ) {
187 224 return 'maemo';
188 - } elseif ( $this->is_MeeGo() ) {
225 + } elseif ( static::is_MeeGo() ) {
189 226 return 'meego';
190 - } elseif ( $this->is_TouchPad() ) {
227 + } elseif ( static::is_TouchPad() ) {
191 228 return 'hp_tablet';
192 - } elseif ( $this->is_facebook_for_iphone() ) {
229 + } elseif ( static::is_facebook_for_iphone() ) {
193 230 return 'facebook-for-iphone';
194 - } elseif ( $this->is_facebook_for_ipad() ) {
231 + } elseif ( static::is_facebook_for_ipad() ) {
195 232 return 'facebook-for-ipad';
196 - } elseif ( $this->is_twitter_for_iphone() ) {
233 + } elseif ( static::is_twitter_for_iphone() ) {
197 234 return 'twitter-for-iphone';
198 - } elseif ( $this->is_twitter_for_ipad() ) {
235 + } elseif ( static::is_twitter_for_ipad() ) {
199 236 return 'twitter-for-ipad';
200 - } elseif ( $this->is_wordpress_for_ios() ) {
237 + } elseif ( static::is_wordpress_for_ios() ) {
201 238 return 'ios-app';
202 - } elseif ( $this->is_iphone_or_ipod( 'iphone-not-safari' ) ) {
239 + } elseif ( static::is_iphone_or_ipod( 'iphone-not-safari' ) ) {
203 240 return 'iphone-unknown';
204 - } elseif ( $this->is_ipad( 'ipad-not-safari' ) ) {
241 + } elseif ( static::is_ipad( 'ipad-not-safari' ) ) {
205 242 return 'ipad-unknown';
206 - } elseif ( $this->is_Nintendo_3DS() ) {
243 + } elseif ( static::is_Nintendo_3DS() ) {
207 244 return 'nintendo-3ds';
208 245 } else {
209 246 $agent = $this->useragent;
210 247 $dumb_agents = $this->dumb_agents;
@@ -226,9 +263,9 @@
226 263 * @return string|bool Name of the platform, false otherwise.
227 264 */
228 265 public function get_platform() {
229 266 if ( isset( $this->platform ) ) {
230 - return $this->platform;
267 + return $this->platform;
231 268 }
232 269
233 270 if ( empty( $this->useragent ) ) {
234 271 return false;
@@ -233,39 +270,40 @@
233 270 if ( empty( $this->useragent ) ) {
234 271 return false;
235 272 }
236 273
237 - if ( strpos( $this->useragent, 'windows phone' ) !== false ) {
238 - $this->platform = self::PLATFORM_WINDOWS;
239 - } elseif ( strpos( $this->useragent, 'windows ce' ) !== false ) {
274 + $ua = strtolower( $this->useragent );
275 + if ( strpos( $ua, 'windows phone' ) !== false ) {
240 276 $this->platform = self::PLATFORM_WINDOWS;
241 - } elseif ( strpos( $this->useragent, 'ipad' ) !== false ) {
277 + } elseif ( strpos( $ua, 'windows ce' ) !== false ) {
278 + $this->platform = self::PLATFORM_WINDOWS;
279 + } elseif ( strpos( $ua, 'ipad' ) !== false ) {
242 280 $this->platform = self::PLATFORM_IPAD;
243 - } elseif ( strpos( $this->useragent, 'ipod' ) !== false ) {
281 + } elseif ( strpos( $ua, 'ipod' ) !== false ) {
244 282 $this->platform = self::PLATFORM_IPOD;
245 - } elseif ( strpos( $this->useragent, 'iphone' ) !== false ) {
283 + } elseif ( strpos( $ua, 'iphone' ) !== false ) {
246 284 $this->platform = self::PLATFORM_IPHONE;
247 - } elseif ( strpos( $this->useragent, 'android' ) !== false ) {
248 - if ( $this->is_android_tablet() ) {
285 + } elseif ( strpos( $ua, 'android' ) !== false ) {
286 + if ( static::is_android_tablet() ) {
249 287 $this->platform = self::PLATFORM_ANDROID_TABLET;
250 288 } else {
251 289 $this->platform = self::PLATFORM_ANDROID;
252 290 }
253 - } elseif ( $this->is_kindle_fire() ) {
291 + } elseif ( static::is_kindle_fire() ) {
254 292 $this->platform = self::PLATFORM_ANDROID_TABLET;
255 - } elseif ( $this->is_blackberry_10() ) {
293 + } elseif ( static::is_blackberry_10() ) {
256 294 $this->platform = self::PLATFORM_BLACKBERRY_10;
257 - } elseif ( strpos( $this->useragent, 'blackberry' ) !== false ) {
295 + } elseif ( strpos( $ua, 'blackberry' ) !== false ) {
258 296 $this->platform = self::PLATFORM_BLACKBERRY;
259 - } elseif ( $this->is_blackberry_tablet() ) {
297 + } elseif ( static::is_blackberry_tablet() ) {
260 298 $this->platform = self::PLATFORM_BLACKBERRY;
261 - } elseif ( $this->is_symbian_platform() ) {
299 + } elseif ( static::is_symbian_platform() ) {
262 300 $this->platform = self::PLATFORM_SYMBIAN;
263 - } elseif ( $this->is_symbian_s40_platform() ) {
301 + } elseif ( static::is_symbian_s40_platform() ) {
264 302 $this->platform = self::PLATFORM_SYMBIAN_S40;
265 - } elseif ( $this->is_J2ME_platform() ) {
303 + } elseif ( static::is_J2ME_platform() ) {
266 304 $this->platform = self::PLATFORM_J2ME_MIDP;
267 - } elseif ( $this->is_firefox_os() ) {
305 + } elseif ( static::is_firefox_os() ) {
268 306 $this->platform = self::PLATFORM_FIREFOX_OS;
269 307 } else {
270 308 $this->platform = false;
271 309 }
@@ -273,8 +311,85 @@
273 311 return $this->platform;
274 312 }
275 313
276 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 + /**
277 392 * This method detects for UA which can display iPhone-optimized web content.
278 393 * Includes iPhone, iPod Touch, Android, WebOS, Fennec (Firefox mobile), etc.
279 394 */
280 395 public function isTierIphone() {
@@ -285,79 +400,79 @@
285 400 $this->matched_agent = 'iphone';
286 401 $this->isTierIphone = true;
287 402 $this->isTierRichCss = false;
288 403 $this->isTierGenericMobile = false;
289 - } elseif ( $this->is_android() ) {
404 + } elseif ( static::is_android() ) {
290 405 $this->matched_agent = 'android';
291 406 $this->isTierIphone = true;
292 407 $this->isTierRichCss = false;
293 408 $this->isTierGenericMobile = false;
294 - } elseif ( $this->is_windows_phone_8() ) {
409 + } elseif ( static::is_windows_phone_8() ) {
295 410 $this->matched_agent = 'winphone8';
296 411 $this->isTierIphone = true;
297 412 $this->isTierRichCss = false;
298 413 $this->isTierGenericMobile = false;
299 - } elseif ( $this->is_WindowsPhone7() ) {
414 + } elseif ( static::is_WindowsPhone7() ) {
300 415 $this->matched_agent = 'win7';
301 416 $this->isTierIphone = true;
302 417 $this->isTierRichCss = false;
303 418 $this->isTierGenericMobile = false;
304 - } elseif ( $this->is_blackberry_10() ) {
419 + } elseif ( static::is_blackberry_10() ) {
305 420 $this->matched_agent = 'blackberry-10';
306 421 $this->isTierIphone = true;
307 422 $this->isTierRichCss = false;
308 423 $this->isTierGenericMobile = false;
309 - } elseif ( $this->is_blackbeberry() && 'blackberry-webkit' === $this->detect_blackberry_browser_version() ) {
424 + } elseif ( static::is_blackbeberry() && 'blackberry-webkit' === static::detect_blackberry_browser_version() ) {
310 425 $this->matched_agent = 'blackberry-webkit';
311 426 $this->isTierIphone = true;
312 427 $this->isTierRichCss = false;
313 428 $this->isTierGenericMobile = false;
314 - } elseif ( $this->is_blackberry_tablet() ) {
429 + } elseif ( static::is_blackberry_tablet() ) {
315 430 $this->matched_agent = 'blackberry_tablet';
316 431 $this->isTierIphone = true;
317 432 $this->isTierRichCss = false;
318 433 $this->isTierGenericMobile = false;
319 - } elseif ( $this->is_PalmWebOS() ) {
434 + } elseif ( static::is_PalmWebOS() ) {
320 435 $this->matched_agent = 'webos';
321 436 $this->isTierIphone = true;
322 437 $this->isTierRichCss = false;
323 438 $this->isTierGenericMobile = false;
324 - } elseif ( $this->is_TouchPad() ) {
439 + } elseif ( static::is_TouchPad() ) {
325 440 $this->matched_agent = 'hp_tablet';
326 441 $this->isTierIphone = true;
327 442 $this->isTierRichCss = false;
328 443 $this->isTierGenericMobile = false;
329 - } elseif ( $this->is_firefox_os() ) {
444 + } elseif ( static::is_firefox_os() ) {
330 445 $this->matched_agent = 'firefoxOS';
331 446 $this->isTierIphone = true;
332 447 $this->isTierRichCss = false;
333 448 $this->isTierGenericMobile = false;
334 - } elseif ( $this->is_firefox_mobile() ) {
449 + } elseif ( static::is_firefox_mobile() ) {
335 450 $this->matched_agent = 'fennec';
336 451 $this->isTierIphone = true;
337 452 $this->isTierRichCss = false;
338 453 $this->isTierGenericMobile = false;
339 - } elseif ( $this->is_opera_mobile() ) {
454 + } elseif ( static::is_opera_mobile() ) {
340 455 $this->matched_agent = 'opera-mobi';
341 456 $this->isTierIphone = true;
342 457 $this->isTierRichCss = false;
343 458 $this->isTierGenericMobile = false;
344 - } elseif ( $this->is_MaemoTablet() ) {
459 + } elseif ( static::is_MaemoTablet() ) {
345 460 $this->matched_agent = 'maemo';
346 461 $this->isTierIphone = true;
347 462 $this->isTierRichCss = false;
348 463 $this->isTierGenericMobile = false;
349 - } elseif ( $this->is_MeeGo() ) {
464 + } elseif ( static::is_MeeGo() ) {
350 465 $this->matched_agent = 'meego';
351 466 $this->isTierIphone = true;
352 467 $this->isTierRichCss = false;
353 468 $this->isTierGenericMobile = false;
354 - } elseif ( $this->is_kindle_touch() ) {
469 + } elseif ( static::is_kindle_touch() ) {
355 470 $this->matched_agent = 'kindle-touch';
356 471 $this->isTierIphone = true;
357 472 $this->isTierRichCss = false;
358 473 $this->isTierGenericMobile = false;
359 - } elseif ( $this->is_Nintendo_3DS() ) {
474 + } elseif ( static::is_Nintendo_3DS() ) {
360 475 $this->matched_agent = 'nintendo-3ds';
361 476 $this->isTierIphone = true;
362 477 $this->isTierRichCss = false;
363 478 $this->isTierGenericMobile = false;
@@ -380,20 +495,20 @@
380 495 return false;
381 496 }
382 497
383 498 // The following devices are explicitly ok.
384 - if ( $this->is_S60_OSSBrowser() ) {
499 + if ( static::is_S60_OSSBrowser() ) {
385 500 $this->matched_agent = 'series60';
386 501 $this->isTierIphone = false;
387 502 $this->isTierRichCss = true;
388 503 $this->isTierGenericMobile = false;
389 - } elseif ( $this->is_opera_mini() ) {
504 + } elseif ( static::is_opera_mini() ) {
390 505 $this->matched_agent = 'opera-mini';
391 506 $this->isTierIphone = false;
392 507 $this->isTierRichCss = true;
393 508 $this->isTierGenericMobile = false;
394 - } elseif ( $this->is_blackbeberry() ) {
395 - $detectedDevice = $this->detect_blackberry_browser_version();
509 + } elseif ( static::is_blackbeberry() ) {
510 + $detectedDevice = static::detect_blackberry_browser_version();
396 511 if (
397 512 'blackberry-5' === $detectedDevice
398 513 || 'blackberry-4.7' === $detectedDevice
399 514 || 'blackberry-4.6' === $detectedDevice
@@ -416,15 +531,16 @@
416 531 *
417 532 * @return bool
418 533 */
419 534 public function is_tablet() {
535 + $ua = $this->useragent;
420 536 return ( 0 // Never true, but makes it easier to manage our list of tablet conditions.
421 - || self::is_ipad()
422 - || self::is_android_tablet()
423 - || self::is_blackberry_tablet()
424 - || self::is_kindle_fire()
425 - || self::is_MaemoTablet()
426 - || self::is_TouchPad()
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 )
427 543 );
428 544 }
429 545
430 546 /**
@@ -449,8 +565,25 @@
449 565 }
450 566 }
451 567
452 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 + /**
453 586 * Detects if the current UA is iPhone Mobile Safari or another iPhone or iPod Touch Browser.
454 587 *
455 588 * They type can check for any iPhone, an iPhone using Safari, or an iPhone using something other than Safari.
456 589 *
@@ -457,16 +590,19 @@
457 590 * Note: If you want to check for Opera mini, Opera mobile or Firefox mobile (or any 3rd party iPhone browser),
458 591 * you should put the check condition before the check for 'iphone-any' or 'iphone-not-safari'.
459 592 * Otherwise those browsers will be 'catched' by the iphone string.
460 593 *
461 - * @param string $type Type of iPhone detection.
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'].
462 596 */
463 - public static function is_iphone_or_ipod( $type = 'iphone-any' ) {
464 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
465 601 return false;
466 602 }
467 603
468 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
604 + $ua = strtolower( wp_unslash( $user_agent ) );
469 605 $is_iphone = ( strpos( $ua, 'iphone' ) !== false ) || ( strpos( $ua, 'ipod' ) !== false );
470 606 $is_safari = ( false !== strpos( $ua, 'safari' ) );
471 607
472 608 if ( 'iphone-safari' === $type ) {
@@ -482,19 +618,22 @@
482 618 * Detects if the current UA is Chrome for iOS
483 619 *
484 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>.
485 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'].
486 624 */
487 - public static function is_chrome_for_iOS() {
488 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
489 628 return false;
490 629 }
491 630
492 - if ( self::is_iphone_or_ipod( 'iphone-safari' ) === false ) {
631 + if ( self::is_iphone_or_ipod( 'iphone-safari', $user_agent ) === false ) {
493 632 return false;
494 633 }
495 634
496 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
635 + $ua = strtolower( wp_unslash( $user_agent ) );
497 636
498 637 if ( strpos( $ua, 'crios/' ) !== false ) {
499 638 return true;
500 639 } else {
@@ -506,15 +645,18 @@
506 645 * Detects if the current UA is Twitter for iPhone
507 646 *
508 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
509 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'].
510 651 */
511 - public static function is_twitter_for_iphone() {
512 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
513 655 return false;
514 656 }
515 657
516 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
658 + $ua = strtolower( wp_unslash( $user_agent ) );
517 659
518 660 if ( strpos( $ua, 'ipad' ) !== false ) {
519 661 return false;
520 662 }
@@ -530,15 +672,18 @@
530 672 * Detects if the current UA is Twitter for iPad
531 673 *
532 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
533 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'].
534 678 */
535 - public static function is_twitter_for_ipad() {
536 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
537 682 return false;
538 683 }
539 684
540 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
685 + $ua = strtolower( wp_unslash( $user_agent ) );
541 686
542 687 if ( strpos( $ua, 'twitter for ipad' ) !== false ) {
543 688 return true;
544 689 } elseif ( strpos( $ua, 'ipad' ) !== false && strpos( $ua, 'twitter for iphone' ) !== false ) {
@@ -552,15 +697,18 @@
552 697 * Detects if the current UA is Facebook for iPhone
553 698 * - Facebook 4020.0 (iPhone; iPhone OS 5.0.1; fr_FR)
554 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]
555 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'].
556 703 */
557 - public static function is_facebook_for_iphone() {
558 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
559 707 return false;
560 708 }
561 709
562 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
710 + $ua = strtolower( wp_unslash( $user_agent ) );
563 711
564 712 if ( false === strpos( $ua, 'iphone' ) ) {
565 713 return false;
566 714 }
@@ -580,15 +728,18 @@
580 728 * Detects if the current UA is Facebook for iPad
581 729 * - Facebook 4020.0 (iPad; iPhone OS 5.0.1; en_US)
582 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]
583 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'].
584 734 */
585 - public static function is_facebook_for_ipad() {
586 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
587 738 return false;
588 739 }
589 740
590 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
741 + $ua = strtolower( wp_unslash( $user_agent ) );
591 742
592 743 if ( false === strpos( $ua, 'ipad' ) ) {
593 744 return false;
594 745 }
@@ -601,15 +752,18 @@
601 752 }
602 753
603 754 /**
604 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'].
605 758 */
606 - public static function is_wordpress_for_ios() {
607 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
608 762 return false;
609 763 }
610 764
611 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
765 + $ua = strtolower( wp_unslash( $user_agent ) );
612 766 if ( false !== strpos( $ua, 'wp-iphone' ) ) {
613 767 return true;
614 768 } else {
615 769 return false;
@@ -623,17 +777,18 @@
623 777 * Note: If you want to check for Opera mini, Opera mobile or Firefox mobile (or any 3rd party iPad browser),
624 778 * you should put the check condition before the check for 'iphone-any' or 'iphone-not-safari'.
625 779 * Otherwise those browsers will be 'catched' by the ipad string.
626 780 *
627 - * @param string $type iPad type.
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'].
628 783 */
629 - public static function is_ipad( $type = 'ipad-any' ) {
630 -
631 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
632 787 return false;
633 788 }
634 789
635 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
790 + $ua = strtolower( wp_unslash( $user_agent ) );
636 791
637 792 $is_ipad = ( false !== strpos( $ua, 'ipad' ) );
638 793 $is_safari = ( false !== strpos( $ua, 'safari' ) );
639 794
@@ -651,16 +806,18 @@
651 806 *
652 807 * See http://www.useragentstring.com/pages/Fennec/
653 808 * Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.1.1) Gecko/20110415 Firefox/4.0.2pre Fennec/4.0.1
654 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'].
655 812 */
656 - public static function is_firefox_mobile() {
657 -
658 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
659 816 return false;
660 817 }
661 818
662 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
819 + $ua = strtolower( wp_unslash( $user_agent ) );
663 820
664 821 if ( strpos( $ua, 'fennec' ) !== false ) {
665 822 return true;
666 823 } else {
@@ -673,16 +830,18 @@
673 830 *
674 831 * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
675 832 * Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion
676 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'].
677 836 */
678 - public static function is_firefox_desktop() {
679 -
680 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
681 840 return false;
682 841 }
683 842
684 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
843 + $ua = strtolower( wp_unslash( $user_agent ) );
685 844
686 845 if ( false !== strpos( $ua, 'firefox' ) && false === strpos( $ua, 'mobile' ) && false === strpos( $ua, 'tablet' ) ) {
687 846 return true;
688 847 } else {
@@ -693,16 +852,18 @@
693 852 /**
694 853 * Detects if the current browser is FirefoxOS Native browser
695 854 *
696 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'].
697 858 */
698 - public static function is_firefox_os() {
699 -
700 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
701 862 return false;
702 863 }
703 864
704 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
865 + $ua = strtolower( wp_unslash( $user_agent ) );
705 866
706 867 if ( strpos( $ua, 'mozilla' ) !== false && strpos( $ua, 'mobile' ) !== false && strpos( $ua, 'gecko' ) !== false && strpos( $ua, 'firefox' ) !== false ) {
707 868 return true;
708 869 } else {
@@ -710,20 +871,75 @@
710 871 }
711 872 }
712 873
713 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 + /**
714 927 * Detect modern Opera desktop
715 928 *
716 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
717 930 *
718 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'].
719 934 */
720 - public static function is_opera_desktop() {
721 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
722 938 return false;
723 939 }
724 940
725 - if ( false === strpos( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ), 'OPR/' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
941 + if ( false === strpos( wp_unslash( $user_agent ), 'OPR/' ) ) {
726 942 return false;
727 943 }
728 944
729 945 return true;
@@ -739,15 +955,18 @@
739 955 * The rendering engine is on Opera's server.)
740 956 *
741 957 * Opera/9.80 (Windows NT 6.1; Opera Mobi/14316; U; en) Presto/2.7.81 Version/11.00"
742 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'].
743 961 */
744 - public static function is_opera_mobile() {
745 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
746 965 return false;
747 966 }
748 967
749 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
968 + $ua = strtolower( wp_unslash( $user_agent ) );
750 969
751 970 if ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'mobi' ) !== false ) {
752 971 return true;
753 972 } elseif ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'nintendo dsi' ) !== false ) {
@@ -766,15 +985,18 @@
766 985 * Opera/9.80 (J2ME/iPhone;Opera Mini/5.0.019802/886; U; ja) Presto/2.4.15
767 986 * Opera/9.80 (J2ME/iPhone;Opera Mini/5.0.019802/886; U; ja) Presto/2.4.15
768 987 * Opera/9.80 (Series 60; Opera Mini/5.1.22783/23.334; U; en) Presto/2.5.25 Version/10.54
769 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'].
770 991 */
771 - public static function is_opera_mini() {
772 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
773 995 return false;
774 996 }
775 997
776 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
998 + $ua = strtolower( wp_unslash( $user_agent ) );
777 999
778 1000 if ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'mini' ) !== false ) {
779 1001 return true;
780 1002 } else {
@@ -784,16 +1006,20 @@
784 1006
785 1007 /**
786 1008 * Detects if the current browser is Opera Mini, but not on a smart device OS(Android, iOS, etc)
787 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'].
788 1012 */
789 - public static function is_opera_mini_dumb() {
790 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
791 1016 return false;
792 1017 }
793 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
794 1018
795 - if ( self::is_opera_mini() ) {
1019 + $ua = strtolower( wp_unslash( $user_agent ) );
1020 +
1021 + if ( self::is_opera_mini( $user_agent ) ) {
796 1022 if ( strpos( $ua, 'android' ) !== false || strpos( $ua, 'iphone' ) !== false || strpos( $ua, 'ipod' ) !== false
797 1023 || strpos( $ua, 'ipad' ) !== false || strpos( $ua, 'blackberry' ) !== false ) {
798 1024 return false;
799 1025 } else {
@@ -804,21 +1030,132 @@
804 1030 }
805 1031 }
806 1032
807 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 + /**
808 1142 * Detects if the current browser is a Windows Phone 7 device.
809 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'].
810 1146 */
811 - public static function is_WindowsPhone7() {
812 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
813 1150 return false;
814 1151 }
815 1152
816 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1153 + $ua = strtolower( wp_unslash( $user_agent ) );
817 1154
818 1155 if ( false === strpos( $ua, 'windows phone os 7' ) ) {
819 1156 return false;
820 - } elseif ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1157 + } elseif ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
821 1158 return false;
822 1159 } else {
823 1160 return true;
824 1161 }
@@ -826,15 +1163,18 @@
826 1163
827 1164 /**
828 1165 * Detects if the current browser is a Windows Phone 8 device.
829 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'].
830 1169 */
831 - public static function is_windows_phone_8() {
832 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
833 1173 return false;
834 1174 }
835 1175
836 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1176 + $ua = strtolower( wp_unslash( $user_agent ) );
837 1177 if ( strpos( $ua, 'windows phone 8' ) === false ) {
838 1178 return false;
839 1179 } else {
840 1180 return true;
@@ -845,19 +1185,22 @@
845 1185 * Detects if the current browser is on a Palm device running the new WebOS. This EXCLUDES TouchPad.
846 1186 *
847 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
848 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'].
849 1191 */
850 - public static function is_PalmWebOS() {
851 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
852 1195 return false;
853 1196 }
854 1197
855 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1198 + $ua = strtolower( wp_unslash( $user_agent ) );
856 1199
857 1200 if ( false === strpos( $ua, 'webos' ) ) {
858 1201 return false;
859 - } elseif ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1202 + } elseif ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
860 1203 return false;
861 1204 } else {
862 1205 return true;
863 1206 }
@@ -867,17 +1210,20 @@
867 1210 * Detects if the current browser is the HP TouchPad default browser. This excludes phones wt WebOS.
868 1211 *
869 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
870 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'].
871 1216 */
872 - public static function is_TouchPad() {
873 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
874 1220 return false;
875 1221 }
876 1222
877 - $http_user_agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1223 + $http_user_agent = strtolower( wp_unslash( $user_agent ) );
878 1224 if ( false !== strpos( $http_user_agent, 'hp-tablet' ) || false !== strpos( $http_user_agent, 'hpwos' ) || false !== strpos( $http_user_agent, 'touchpad' ) ) {
879 - if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1225 + if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
880 1226 return false;
881 1227 } else {
882 1228 return true;
883 1229 }
@@ -893,17 +1239,19 @@
893 1239 *
894 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
895 1241 *
896 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'].
897 1245 */
898 - public static function is_S60_OSSBrowser() {
899 -
900 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
901 1249 return false;
902 1250 }
903 1251
904 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
905 - if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
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 ) ) {
906 1254 return false;
907 1255 }
908 1256
909 1257 $pos_webkit = strpos( $agent, 'webkit' );
@@ -924,16 +1272,18 @@
924 1272 }
925 1273
926 1274 /**
927 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'].
928 1278 */
929 - public static function is_symbian_platform() {
930 -
931 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
932 1282 return false;
933 1283 }
934 1284
935 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1285 + $agent = strtolower( wp_unslash( $user_agent ) );
936 1286
937 1287 $pos_webkit = strpos( $agent, 'webkit' );
938 1288 if ( false !== $pos_webkit ) {
939 1289 // First, test for WebKit, then make sure it's either Symbian or S60.
@@ -958,16 +1308,18 @@
958 1308 /**
959 1309 * Detects if the device platform is the Symbian Series 40.
960 1310 * Nokia Browser for Series 40 is a proxy based browser, previously known as Ovi Browser.
961 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'].
962 1314 */
963 - public static function is_symbian_s40_platform() {
964 -
965 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
966 1318 return false;
967 1319 }
968 1320
969 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1321 + $agent = strtolower( wp_unslash( $user_agent ) );
970 1322
971 1323 if ( strpos( $agent, 'series40' ) !== false ) {
972 1324 if ( strpos( $agent, 'nokia' ) !== false || strpos( $agent, 'ovibrowser' ) !== false || strpos( $agent, 'nokiabrowser' ) !== false ) {
973 1325 return true;
@@ -979,17 +1331,19 @@
979 1331
980 1332 /**
981 1333 * Returns if the device belongs to J2ME capable family.
982 1334 *
1335 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1336 + *
983 1337 * @return bool
984 1338 */
985 - public static function is_J2ME_platform() {
986 -
987 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
988 1342 return false;
989 1343 }
990 1344
991 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1345 + $agent = strtolower( wp_unslash( $user_agent ) );
992 1346
993 1347 if ( strpos( $agent, 'j2me/midp' ) !== false ) {
994 1348 return true;
995 1349 } elseif ( strpos( $agent, 'midp' ) !== false && strpos( $agent, 'cldc' ) ) {
@@ -999,16 +1353,18 @@
999 1353 }
1000 1354
1001 1355 /**
1002 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'].
1003 1359 */
1004 - public static function is_MaemoTablet() {
1005 -
1006 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1007 1363 return false;
1008 1364 }
1009 1365
1010 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1366 + $agent = strtolower( wp_unslash( $user_agent ) );
1011 1367
1012 1368 $pos_maemo = strpos( $agent, 'maemo' );
1013 1369 if ( false === $pos_maemo ) {
1014 1370 return false;
@@ -1015,9 +1371,9 @@
1015 1371 }
1016 1372
1017 1373 // Must be Linux + Tablet, or else it could be something else.
1018 1374 if ( strpos( $agent, 'tablet' ) !== false && strpos( $agent, 'linux' ) !== false ) {
1019 - if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1375 + if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1020 1376 return false;
1021 1377 } else {
1022 1378 return true;
1023 1379 }
@@ -1027,20 +1383,22 @@
1027 1383 }
1028 1384
1029 1385 /**
1030 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'].
1031 1389 */
1032 - public static function is_MeeGo() {
1033 -
1034 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1035 1393 return false;
1036 1394 }
1037 1395
1038 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1396 + $ua = strtolower( wp_unslash( $user_agent ) );
1039 1397
1040 1398 if ( false === strpos( $ua, 'meego' ) ) {
1041 1399 return false;
1042 - } elseif ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1400 + } elseif ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1043 1401 return false;
1044 1402 } else {
1045 1403 return true;
1046 1404 }
@@ -1047,16 +1405,18 @@
1047 1405 }
1048 1406
1049 1407 /**
1050 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'].
1051 1411 */
1052 - public static function is_webkit() {
1053 -
1054 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1055 1415 return false;
1056 1416 }
1057 1417
1058 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1418 + $agent = strtolower( wp_unslash( $user_agent ) );
1059 1419
1060 1420 $pos_webkit = strpos( $agent, 'webkit' );
1061 1421
1062 1422 if ( false !== $pos_webkit ) {
@@ -1068,19 +1428,22 @@
1068 1428
1069 1429 /**
1070 1430 * Detects if the current browser is the Native Android browser.
1071 1431 *
1432 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1433 + *
1072 1434 * @return boolean true if the browser is Android otherwise false
1073 1435 */
1074 - public static function is_android() {
1075 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1076 1439 return false;
1077 1440 }
1078 1441
1079 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1442 + $agent = strtolower( wp_unslash( $user_agent ) );
1080 1443 $pos_android = strpos( $agent, 'android' );
1081 1444 if ( false !== $pos_android ) {
1082 - if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1445 + if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1083 1446 return false;
1084 1447 } else {
1085 1448 return true;
1086 1449 }
@@ -1092,16 +1455,19 @@
1092 1455 /**
1093 1456 * Detects if the current browser is the Native Android Tablet browser.
1094 1457 * Assumes 'Android' should be in the user agent, but not 'mobile'
1095 1458 *
1459 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1460 + *
1096 1461 * @return boolean true if the browser is Android and not 'mobile' otherwise false
1097 1462 */
1098 - public static function is_android_tablet() {
1099 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1100 1466 return false;
1101 1467 }
1102 1468
1103 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1469 + $agent = strtolower( wp_unslash( $user_agent ) );
1104 1470
1105 1471 $pos_android = strpos( $agent, 'android' );
1106 1472 $pos_mobile = strpos( $agent, 'mobile' );
1107 1473 $post_android_app = strpos( $agent, 'wp-android' );
@@ -1106,9 +1472,9 @@
1106 1472 $pos_mobile = strpos( $agent, 'mobile' );
1107 1473 $post_android_app = strpos( $agent, 'wp-android' );
1108 1474
1109 1475 if ( false !== $pos_android && false === $pos_mobile && false === $post_android_app ) {
1110 - if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1476 + if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1111 1477 return false;
1112 1478 } else {
1113 1479 return true;
1114 1480 }
@@ -1122,16 +1488,19 @@
1122 1488 *
1123 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
1124 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
1125 1491 *
1492 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1493 + *
1126 1494 * @return boolean true if the browser is Kindle Fire Native browser otherwise false
1127 1495 */
1128 - public static function is_kindle_fire() {
1129 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1130 1499 return false;
1131 1500 }
1132 1501
1133 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1502 + $agent = strtolower( wp_unslash( $user_agent ) );
1134 1503 $pos_silk = strpos( $agent, 'silk/' );
1135 1504 $pos_silk_acc = strpos( $agent, 'silk-accelerated=' );
1136 1505 if ( false !== $pos_silk && false !== $pos_silk_acc ) {
1137 1506 return true;
@@ -1144,17 +1513,21 @@
1144 1513 * Detects if the current browser is the Kindle Touch Native browser
1145 1514 *
1146 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+
1147 1516 *
1517 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1518 + *
1148 1519 * @return boolean true if the browser is Kindle monochrome Native browser otherwise false
1149 1520 */
1150 - public static function is_kindle_touch() {
1151 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1152 1524 return false;
1153 1525 }
1154 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1526 +
1527 + $agent = strtolower( wp_unslash( $user_agent ) );
1155 1528 $pos_kindle_touch = strpos( $agent, 'kindle/3.0+' );
1156 - if ( false !== $pos_kindle_touch && false === self::is_kindle_fire() ) {
1529 + if ( false !== $pos_kindle_touch && false === self::is_kindle_fire( $user_agent ) ) {
1157 1530 return true;
1158 1531 } else {
1159 1532 return false;
1160 1533 }
@@ -1161,15 +1534,18 @@
1161 1534 }
1162 1535
1163 1536 /**
1164 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'].
1165 1540 */
1166 - public static function is_windows8_auth() {
1167 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1168 1544 return false;
1169 1545 }
1170 1546
1171 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1547 + $agent = strtolower( wp_unslash( $user_agent ) );
1172 1548 $pos = strpos( $agent, 'msauthhost' );
1173 1549 if ( false !== $pos ) {
1174 1550 return true;
1175 1551 } else {
@@ -1178,15 +1554,18 @@
1178 1554 }
1179 1555
1180 1556 /**
1181 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'].
1182 1560 */
1183 - public static function is_wordpress_for_win8() {
1184 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1185 1564 return false;
1186 1565 }
1187 1566
1188 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1567 + $agent = strtolower( wp_unslash( $user_agent ) );
1189 1568 $pos = strpos( $agent, 'wp-windows8' );
1190 1569 if ( false !== $pos ) {
1191 1570 return true;
1192 1571 } else {
@@ -1195,15 +1574,18 @@
1195 1574 }
1196 1575
1197 1576 /**
1198 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'].
1199 1580 */
1200 - public static function is_wordpress_desktop_app() {
1201 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1202 1584 return false;
1203 1585 }
1204 1586
1205 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1587 + $agent = strtolower( wp_unslash( $user_agent ) );
1206 1588 $pos = strpos( $agent, 'WordPressDesktop' );
1207 1589 if ( false !== $pos ) {
1208 1590 return true;
1209 1591 } else {
@@ -1214,16 +1596,18 @@
1214 1596 /**
1215 1597 * The is_blackberry_tablet() method can be used to check the User Agent for a RIM blackberry tablet.
1216 1598 * The user agent of the BlackBerry® Tablet OS follows a format similar to the following:
1217 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'].
1218 1602 */
1219 - public static function is_blackberry_tablet() {
1220 -
1221 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1222 1606 return false;
1223 1607 }
1224 1608
1225 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1609 + $agent = strtolower( wp_unslash( $user_agent ) );
1226 1610 $pos_playbook = stripos( $agent, 'PlayBook' );
1227 1611 $pos_rim_tablet = stripos( $agent, 'RIM Tablet' );
1228 1612
1229 1613 if ( ( false === $pos_playbook ) || ( false === $pos_rim_tablet ) ) {
@@ -1235,19 +1619,22 @@
1235 1619
1236 1620 /**
1237 1621 * The is_blackbeberry() method can be used to check the User Agent for a blackberry device.
1238 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'].
1239 1625 */
1240 - public static function is_blackbeberry() {
1241 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1242 1629 return false;
1243 1630 }
1244 1631
1245 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1632 + $agent = strtolower( wp_unslash( $user_agent ) );
1246 1633
1247 1634 $pos_blackberry = strpos( $agent, 'blackberry' );
1248 1635 if ( false !== $pos_blackberry ) {
1249 - if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() ) {
1636 + if ( self::is_opera_mini( $user_agent ) || self::is_opera_mobile( $user_agent ) || self::is_firefox_mobile( $user_agent ) ) {
1250 1637 return false;
1251 1638 } else {
1252 1639 return true;
1253 1640 }
@@ -1257,18 +1644,98 @@
1257 1644 }
1258 1645
1259 1646 /**
1260 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'].
1261 1650 */
1262 - public static function is_blackberry_10() {
1263 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1264 1654 return false;
1265 1655 }
1266 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1656 +
1657 + $agent = strtolower( wp_unslash( $user_agent ) );
1267 1658 return ( strpos( $agent, 'bb10' ) !== false ) && ( strpos( $agent, 'mobile' ) !== false );
1268 1659 }
1269 1660
1270 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 + /**
1271 1738 * Retrieve the blackberry OS version.
1272 1739 *
1273 1740 * Return strings are from the following list:
1274 1741 * - blackberry-10
@@ -1279,22 +1746,24 @@
1279 1746 * - blackberry-4.7
1280 1747 * - blackberry-4.6
1281 1748 * - blackberry-4.5
1282 1749 *
1750 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1751 + *
1283 1752 * @return string Version of the BB OS.
1284 1753 * If version is not found, get_blackbeberry_OS_version will return boolean false.
1285 1754 */
1286 - public static function get_blackbeberry_OS_version() {
1287 -
1288 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1289 1758 return false;
1290 1759 }
1291 1760
1292 - if ( self::is_blackberry_10() ) {
1761 + if ( self::is_blackberry_10( $user_agent ) ) {
1293 1762 return 'blackberry-10';
1294 1763 }
1295 1764
1296 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1765 + $agent = strtolower( wp_unslash( $user_agent ) );
1297 1766
1298 1767 $pos_blackberry = stripos( $agent, 'blackberry' );
1299 1768 if ( false === $pos_blackberry ) {
1300 1769 // Not a blackberry device.
@@ -1313,9 +1782,9 @@
1313 1782 return 'blackberry-torch'; // Match the torch first edition. the 2nd edition should use the OS7 and doesn't need any special rule.
1314 1783 } elseif ( preg_match( '#Version\/([\d\.]+)#i', $agent, $matches ) ) { // Detecting the BB OS version for devices running OS 6.0 or higher.
1315 1784 $version = $matches[1];
1316 1785 $version_num = explode( '.', $version );
1317 - if ( false === is_array( $version_num ) || count( $version_num ) <= 1 ) {
1786 + if ( count( $version_num ) <= 1 ) {
1318 1787 return 'blackberry-6'; // not a BB device that match our rule.
1319 1788 } else {
1320 1789 return 'blackberry-' . $version_num[0];
1321 1790 }
@@ -1334,9 +1803,9 @@
1334 1803 }
1335 1804
1336 1805 $version_num = explode( '.', $version );
1337 1806
1338 - if ( is_array( $version_num ) === false || count( $version_num ) <= 1 ) {
1807 + if ( count( $version_num ) <= 1 ) {
1339 1808 return false;
1340 1809 }
1341 1810
1342 1811 $version_num_major = (int) $version_num[0];
@@ -1364,20 +1833,22 @@
1364 1833 * - blackberry-5
1365 1834 * - blackberry-4.7
1366 1835 * - blackberry-4.6
1367 1836 *
1837 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1838 + *
1368 1839 * @return string Type of the BB browser.
1369 1840 * If browser's version is not found, detect_blackbeberry_browser_version will return boolean false.
1370 1841 */
1371 - public static function detect_blackberry_browser_version() {
1372 -
1373 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1374 1845 return false;
1375 1846 }
1376 1847
1377 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1848 + $agent = strtolower( wp_unslash( $user_agent ) );
1378 1849
1379 - if ( self::is_blackberry_10() ) {
1850 + if ( self::is_blackberry_10( $user_agent ) ) {
1380 1851 return 'blackberry-10';
1381 1852 }
1382 1853
1383 1854 $pos_blackberry = strpos( $agent, 'blackberry' );
@@ -1396,9 +1867,9 @@
1396 1867 }
1397 1868
1398 1869 $version_num = explode( '.', $matches[1] );
1399 1870
1400 - if ( false === is_array( $version_num ) || count( $version_num ) <= 1 ) {
1871 + if ( count( $version_num ) <= 1 ) {
1401 1872 return false;
1402 1873 }
1403 1874
1404 1875 $version_num_major = (int) $version_num[0];
@@ -1419,17 +1890,19 @@
1419 1890
1420 1891 /**
1421 1892 * Checks if a visitor is coming from one of the WordPress mobile apps.
1422 1893 *
1894 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1895 + *
1423 1896 * @return bool
1424 1897 */
1425 - public static function is_mobile_app() {
1426 -
1427 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1428 1901 return false;
1429 1902 }
1430 1903
1431 - $agent = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1904 + $agent = strtolower( wp_unslash( $user_agent ) );
1432 1905
1433 1906 if ( isset( $_SERVER['X_USER_AGENT'] ) && preg_match( '|wp-webos|', $_SERVER['X_USER_AGENT'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
1434 1907 return true; // Wp4webos 1.1 or higher.
1435 1908 }
@@ -1453,15 +1926,18 @@
1453 1926 * Detects if the current browser is Nintendo 3DS handheld.
1454 1927 *
1455 1928 * Example: Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7498.US
1456 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'].
1457 1932 */
1458 - public static function is_Nintendo_3DS() {
1459 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
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 ) ) {
1460 1936 return false;
1461 1937 }
1462 1938
1463 - $ua = strtolower( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1939 + $ua = strtolower( wp_unslash( $user_agent ) );
1464 1940 if ( strpos( $ua, 'nintendo 3ds' ) !== false ) {
1465 1941 return true;
1466 1942 }
1467 1943 return false;
@@ -1469,22 +1945,34 @@
1469 1945
1470 1946 /**
1471 1947 * Was the current request made by a known bot?
1472 1948 *
1949 + * @param string|null $user_agent Optional. User agent string to check. If not provided, uses $_SERVER['HTTP_USER_AGENT'].
1950 + *
1473 1951 * @return boolean
1474 1952 */
1475 - public static function is_bot() {
1953 + public static function is_bot( $user_agent = null ) {
1476 1954 static $is_bot = null;
1477 1955
1478 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1479 - return false;
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;
1480 1967 }
1481 1968
1482 - if ( $is_bot === null ) {
1483 - $is_bot = self::is_bot_user_agent( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is validating.
1969 + if ( empty( $user_agent ) ) {
1970 + return false;
1484 1971 }
1485 1972
1486 - return $is_bot;
1973 + // Don't use cache when a custom user agent is provided.
1974 + return self::is_bot_user_agent( $user_agent );
1487 1975 }
1488 1976
1489 1977 /**
1490 1978 * Is the given user-agent a known bot?
@@ -1499,71 +1987,401 @@
1499 1987 if ( empty( $ua ) ) {
1500 1988 return false;
1501 1989 }
1502 1990
1991 + // Some sourced via
1992 + // https://github.com/ua-parser/uap-core/blob/432e95f6767cc8bab4c20c255784cd6f7e93bc15/regexes.yaml#L151
1503 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
1504 2055 'alexa',
1505 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',
1506 2062 'ask jeeves',
1507 2063 'attentio',
1508 2064 'baiduspider',
1509 - 'bingbot',
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',
1510 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',
1511 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
1512 2104 'fastmobilecrawl',
1513 - 'feedfetcher-google',
2105 + 'feed seeker bot',
2106 + 'feedbin',
2107 + 'feedburner',
2108 + 'finderbots',
2109 + 'findlinks',
1514 2110 'firefly',
2111 + 'flamingo_searchengine',
2112 + 'followsite bot',
1515 2113 'froogle',
2114 + 'furlbot',
2115 + 'genieo',
2116 + 'germcrawler',
1516 2117 'gigabot',
1517 - 'googlebot',
1518 - 'googlebot-mobile',
2118 + 'gomezagent',
2119 + 'gonzo1',
2120 + 'grapeshotcrawler',
2121 + 'grokkit-crawler',
2122 + 'grub-client',
2123 + 'gsa-crawler',
1519 2124 'heritrix',
2125 + 'hiddenmarket',
2126 + 'holmes',
2127 + 'hoowwwer',
2128 + 'htdig',
1520 2129 'httrack',
1521 2130 'ia_archiver',
1522 - 'irlbot',
2131 + 'icarus6j',
2132 + 'icc-crawler',
2133 + 'ichiro',
2134 + 'iconsurf',
1523 2135 'iescholar',
2136 + 'iltrovatore',
2137 + 'index crawler',
1524 2138 'infoseek',
2139 + 'infuzapp',
2140 + 'innovazion crawler',
2141 + 'internetarchive',
2142 + 'irlbot',
2143 + 'jbot',
2144 + 'job roboter',
1525 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',
1526 2156 'linkcheck',
2157 + 'linkdexbot',
2158 + 'linkedinbot',
2159 + 'linkfluence', // http://linkfluence.com/
2160 + 'linkwalker', // https://www.linkwalker.com/
2161 + 'lite bot',
2162 + 'livelapbot',
2163 + 'llaut',
1527 2164 'lycos',
2165 + 'mail.ru_bot',
2166 + 'masidani_bot',
2167 + 'masscan',
1528 2168 'mediapartners',
1529 2169 'mediobot',
2170 + 'mj12bot',
2171 + 'mogimogi',
2172 + 'mojeekbot', // https://www.mojeek.com/bot.html
1530 2173 'motionbot',
2174 + 'mozdex',
2175 + 'mshots',
1531 2176 'msnbot',
1532 - 'mshots',
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',
1533 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',
1534 2200 'pss-webkit-request',
1535 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',
1536 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',
1537 2226 'slurp',
1538 2227 'snapbot',
2228 + 'snapchat', // https://developers.snap.com/robots
2229 + 'snappy',
2230 + 'speedy spider',
1539 2231 'spider',
2232 + 'squrl java',
2233 + 'stringer',
1540 2234 'taptubot',
1541 2235 'technoratisnoop',
1542 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',
1543 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',
1544 2268 'yahooseeker',
1545 2269 'yahooysmcm',
1546 2270 'yammybot',
1547 - 'ahrefsbot',
1548 - 'pingdom.com_bot',
1549 - 'kraken',
1550 2271 'yandexbot',
1551 - 'twitterbot',
1552 - 'tweetmemebot',
1553 - 'openhosebot',
1554 - 'queryseekerspider',
1555 - 'linkdexbot',
1556 - 'grokkit-crawler',
1557 - 'livelapbot',
1558 - 'germcrawler',
1559 - 'domaintunocrawler',
1560 - 'grapeshotcrawler',
1561 - 'cloudflare-alwaysonline',
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',
1562 2281 );
1563 2282
1564 2283 foreach ( $bot_agents as $bot_agent ) {
1565 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 ) ) {
1566 2384 return true;
1567 2385 }
1568 2386 }
1569 2387