PluginProbe
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation / trunk
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation vtrunk
1.4.48 1.4.47 1.4.46 1.4.45 1.4.44 1.4.43 1.4.42 1.4.41 1.4.40 1.4.39 1.4.38 1.4.37 1.4.36 1.1.2 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 64 releases
optin / includes / utils / class-device.php

class-device.php in WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation trunk, at includes/utils/class-device.php

169 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace OPTN\Includes\Utils;
4
5 class Device {
6
7 /**
8 * Cache, keyed by user agent + detection mode.
9 *
10 * The mode is part of the key because `$ua_only` changes the answer: a lookup that
11 * passes an explicit UA must not reuse (or poison) a lookup that is allowed to read
12 * the current request's client hints.
13 *
14 * @var array
15 */
16 private static $device = array();
17
18 /**
19 * Patterns that identify a phone.
20 *
21 * Evaluated before the tablet list. A UA carrying an explicit phone marker is a phone
22 * even when it also matches a tablet pattern - every Android phone contains the string
23 * "Android", and every iPhone contains "Mobile".
24 *
25 * @var string
26 */
27 protected static $phone_devices = 'iPhone|iPod|Windows Phone|IEMobile|Opera Mini|Opera Mobi|BlackBerry|BB10|Android.*\bMobile\b';
28
29 /**
30 * List of tablet devices.
31 *
32 * @var array
33 */
34 protected static $tablet_devices = array(
35 'iPad' => 'iPad|iPad.*Mobile',
36 'GenericTablet' => array(
37 'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002',
38 '\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab',
39 '\bM6pro\b|CT1020W|arc 10HD|\bTP750\b|\bQTAQZ3\b|WVT101|TM1088|KT107',
40 'Android(?!.*\bMobile\b)|Android 3\.0|xoom|sch-i800|playbook|kindle|\bNexus (?:7|9|10)\b',
41 ),
42 );
43
44 /**
45 * Whether the user agent identifies a phone.
46 *
47 * @param string $ua user agent.
48 * @return boolean
49 */
50 private static function is_phone( $ua ) {
51 return self::match( self::$phone_devices, $ua );
52 }
53
54 /**
55 * Whether the user agent identifies a tablet.
56 *
57 * @param string $ua user agent.
58 * @return boolean
59 */
60 private static function is_tablet( $ua ) {
61 foreach ( self::$tablet_devices as $_regex ) {
62 $regexString = $_regex;
63 if ( is_array( $_regex ) ) {
64 $regexString = implode( '|', $_regex );
65 }
66 if ( self::match( $regexString, $ua ) ) {
67 return true;
68 }
69 }
70
71 return false;
72 }
73
74 /**
75 * Whether the browser itself declares this request as coming from a phone.
76 *
77 * Chromium sends `Sec-CH-UA-Mobile: ?1` only for phones - Android tablets and desktops
78 * both get `?0` - so it is a stronger signal than any user agent pattern and is checked
79 * first. Treated as a positive signal only: `?0` never suppresses the checks below,
80 * because most non-Chromium browsers omit the header entirely.
81 *
82 * @param boolean $ua_only ignore the current request's client hints.
83 * @return boolean
84 */
85 private static function is_phone_by_client_hint( $ua_only = false ) {
86 if ( $ua_only || ! isset( $_SERVER['HTTP_SEC_CH_UA_MOBILE'] ) ) {
87 return false;
88 }
89
90 return '?1' === sanitize_text_field( wp_unslash( $_SERVER['HTTP_SEC_CH_UA_MOBILE'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
91 }
92
93 /**
94 * Fallback mobile check for user agents that matched neither the phone nor the
95 * tablet list.
96 *
97 * @param string $ua user agent.
98 * @return boolean
99 */
100 private static function is_mobile( $ua ) {
101 return str_contains( $ua, 'Mobile' )
102 || str_contains( $ua, 'Android' )
103 || str_contains( $ua, 'Silk/' )
104 || str_contains( $ua, 'Kindle' )
105 || str_contains( $ua, 'BlackBerry' )
106 || str_contains( $ua, 'Opera Mini' )
107 || str_contains( $ua, 'Opera Mobi' );
108 }
109
110 /**
111 * Resolve the device breakpoint for a user agent.
112 *
113 * @param string $ua user agent. Defaults to the current request's.
114 * @return string one of 'lg' (desktop), 'sm' (tablet), 'xs' (phone).
115 */
116 public static function get_device( $ua = '' ) {
117 $ua_only = false;
118
119 if ( ! empty( $ua ) ) {
120 $ua_only = true;
121 } else {
122 $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
123 }
124
125 if ( empty( $ua ) ) {
126 return 'lg';
127 }
128
129 $cache_key = $ua . '|' . ( $ua_only ? '1' : '0' );
130
131 // Check cache
132 if ( isset( self::$device[ $cache_key ] ) ) {
133 return self::$device[ $cache_key ];
134 }
135
136 // Order matters. The client hint is authoritative where present, and an explicit
137 // phone marker outranks the tablet list, which is deliberately broad enough to
138 // match phones too (see `Android(?!.*Mobile)`). The generic mobile check is last
139 // because it is the loosest.
140 if ( self::is_phone_by_client_hint( $ua_only ) || self::is_phone( $ua ) ) {
141 $device = 'xs';
142 } elseif ( self::is_tablet( $ua ) ) {
143 $device = 'sm';
144 } elseif ( self::is_mobile( $ua ) ) {
145 $device = 'xs';
146 } else {
147 $device = 'lg';
148 }
149
150 self::$device[ $cache_key ] = $device;
151
152 return $device;
153 }
154
155 /**
156 *
157 * @param string $regex
158 *
159 * @return boolean
160 */
161 private static function match( $regex, $ua ) {
162 if ( empty( $ua ) ) {
163 return false;
164 }
165
166 return (bool) preg_match( sprintf( '#%s#is', $regex ), $ua, $matches );
167 }
168 }
169