From f1a164ebfa8df51fcb3aa6d98a316360123361ba Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 6 Feb 2026 19:27:28 -0600 Subject: [PATCH 1/6] Incorporate browse happy API & update browser comparison. Signed-off-by: Joe Dolson --- inc/version-check/namespace.php | 474 +++++++++++++++++++++++++++++++- 1 file changed, 467 insertions(+), 7 deletions(-) diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index f8379f35..ec8d90e3 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -87,13 +87,9 @@ function get_browser_check_response( string $agent ) { // Switch delimiter to avoid conflicts. $regex = '#' . trim( BROWSER_REGEX, '/' ) . '#'; $supported = preg_match( $regex, $agent, $matches ); + $data = parse_user_agent( $agent ); - return [ - 'response' => [ - 'code' => 200, - 'message' => 'OK', - ], - 'body' => json_encode( [ + $default_data = [ 'platform' => _x( 'your platform', 'operating system check', 'fair' ), 'name' => _x( 'your browser', 'browser version check', 'fair' ), 'version' => '', @@ -103,7 +99,15 @@ function get_browser_check_response( string $agent ) { 'update_url' => 'https://browsehappy.com/', 'img_src' => '', 'img_src_ssl' => '', - ] ), + ]; + $data = array_merge( $default_data, $data ); + + return [ + 'response' => [ + 'code' => 200, + 'message' => 'OK', + ], + 'body' => json_encode( $data ), 'headers' => [], 'cookies' => [], 'http_response_code' => 200, @@ -262,3 +266,459 @@ function get_server_check_response( string $version ) { 'http_response_code' => 200, ]; } + +/** + * Returns current version numbers for all browsers. + * + * These are for major release branches, not full build numbers. + * Firefox 3.6, 4, etc., not Chrome 11.0.696.65. + * + * @return array Associative array of browser names with their respective + * current (or somewhat current) version number. + */ +function get_browser_current_versions() { + return [ + 'Chrome' => '18', // Lowest version at the moment (mobile) + 'Firefox' => '56', + 'Microsoft Edge' => '15.15063', + 'Opera' => '12.18', + 'Safari' => '11', + 'Internet Explorer' => '11', + ]; +} + +/** + * Returns browser data for a given browser. + * + * @param string|false $browser The name of the browser. Default false. + * @return false|array|object { + * Array of data objects about browsers. False if the browser is unknown. + * + * @type string $name Name of the browser. + * @type string $url The home URL for the browser. + * @type string $img_src The non-HTTPs URL for the browser's logo image. + * @type string $img_src_ssl The HTTPS URL for the browser's logo image. + * } + */ +function get_browser_data( $browser = false ) { + + $data = [ + 'Internet Explorer' => (object) [ + 'name' => 'Internet Explorer', + 'url' => 'https://support.microsoft.com/help/17621/internet-explorer-downloads', + ], + 'Edge' => (object) [ + 'name' => 'Microsoft Edge', + 'url' => 'https://www.microsoft.com/edge', + ], + 'Firefox' => (object) [ + 'name' => 'Mozilla Firefox', + 'url' => 'https://www.mozilla.org/firefox/', + ], + 'Safari' => (object) [ + 'name' => 'Safari', + 'url' => 'https://www.apple.com/safari/', + ], + 'Opera' => (object) [ + 'name' => 'Opera', + 'url' => 'https://www.opera.com/', + ], + 'Chrome' => (object) [ + 'name' => 'Google Chrome', + 'url' => 'https://www.google.com/chrome', + ], + ]; + + if ( false === $browser ) { + return $data; + } + + if ( ! isset( $data[ $browser ] ) ) { + return false; + } + + return $data[ $browser ]; +} + +/** + * Returns an associative array of explicit browser token names and their + * associated info. + * + * Explicit tokens are tokens that, if present, indicate a specific browser. + * + * If a browser is not identified by an explicit token, or s special + * handling not supported by the default handler, then a new conditional block + * for the browser instead needs to be added in parse_user_agent(). + * + * In any case, the browser token name also needs to be added to the regex for + * browser tokens in parse_user_agent(). + * + * @return array { + * Associative array of browser tokens and their associated data. + * + * @type array $data { + * Associative array of browser data. All are optional. + * + * @type string $name Name of browser, if it differs from the + * token name. Default is token name. + * @type bool $use_version Should the 'Version' token, if present, + * supercede the version associated with the + * browser token? Default false. + * @type bool $mobile Does the browser signify the platform is + * mobile (for situations where it may no + * already be apparent)? Default false. + * @type string $platform The name of the platform, to supercede + * whatever platform may have been detected. + * Default empty string. + * } + * } + */ +function get_explicit_browser_tokens() { + return [ + 'Camino' => [], + 'Chromium' => [], + 'Edge' => [ + 'name' => 'Microsoft Edge', + ], + 'Kindle' => [ + 'name' => 'Kindle Browser', + 'use_version' => true, + ], + 'Konqueror' => [], + 'konqueror' => [ + 'name' => 'Konqueror', + ], + 'NokiaBrowser' => [ + 'name' => 'Nokia Browser', + 'mobile' => true, + ], + 'Opera Mini' => [ // Must be before 'Opera' + 'mobile' => true, + 'use_version' => true, + ], + 'Opera' => [ + 'use_version' => true, + ], + 'OPR' => [ + 'name' => 'Opera', + 'use_version' => true, + ], + 'PaleMoon' => [ + 'name' => 'Pale Moon', + ], + 'QQBrowser' => [ + 'name' => 'QQ Browser', + ], + 'RockMelt' => [], + 'SamsungBrowser' => [ + 'name' => 'Samsung Browser', + ], + 'SeaMonkey' => [], + 'Silk' => [ + 'name' => 'Amazon Silk', + ], + 'S40OviBrowser' => [ + 'name' => 'Ovi Browser', + 'mobile' => true, + 'platform' => 'Symbian', + ], + 'UCBrowser' => [ // Must be before 'UCWEB' + 'name' => 'UC Browser', + ], + 'UCWEB' => [ + 'name' => 'UC Browser', + ], + 'Vivaldi' => [], + 'IEMobile' => [ // Keep last just in case + 'name' => 'Internet Explorer Mobile', + ], + ]; +} + +/** + * Parses a user agent string into its important parts. + * + * @param string $user_agent The user agent string for a browser. + * @return array { + * Array containing data based on the parsing of the user agent. + * + * @type string $platform The platform running the browser. + * @type string $name The name of the browser. + * @type string $version The reported version of the browser. + * @type string $update_url The URL to obtain the update for the browser. + * @type string $img_src The non-HTTPS URL for the browser's logo image. + * @type string $img_src_ssl The HTTPS URL for the browser's logo image. + * @type string $current_version The current latest version of the browser. + * @type bool $upgrade Is there an update available for the browser? + * @type bool $insecure Is the browser insecure? + * @type bool $mobile Is the browser on a mobile platform? + * } + */ +function parse_user_agent( $user_agent ) { + $data = [ + 'name' => '', + 'version' => '', + 'platform' => '', + 'update_url' => '', + 'img_src' => '', + 'img_src_ssl' => '', + 'current_version' => '', + 'upgrade' => false, + 'insecure' => false, + 'mobile' => false, + ]; + $mobile_device = ''; + + // Identify platform/OS in user-agent string. + if ( preg_match( + '/(?P' // Capture subpattern matches into 'platform' array + . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens + . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens + . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens + . ')' + . '(?:' + . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers + . ')*' + . '(?:' + . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64) + . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number + . ')*' + . '(;|\))' // Ending in a semi-colon or close parenthesis + . '/im', // Case insensitive, multiline + $user_agent, + $regs + ) ) { + $data['platform'] = $regs['platform']; + } + + // Find tokens of interest in user-agent string. + preg_match_all( + '%(?P' // Capture subpattern matches into the 'name' array + . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens + . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens + . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens + . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens + . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens + . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens + . '|Version' // Version token + . ')' + . '(?:' + . '[/ ]' // Forward slash or space + . ')' + . '(?P' // Capture subpattern matches into 'version' array + . '[0-9.]+' // One or more numbers and/or decimal points + . ')' + . '%im', // Case insensitive, multiline + $user_agent, + $result, + PREG_PATTERN_ORDER + ); + + // Create associative array with tokens as keys and versions as values. + $tokens = array_combine( array_reverse( $result['name'] ), array_reverse( $result['version'] ) ); + + // Properly set platform if Android is actually being reported. + if ( 'Linux' === $data['platform'] && false !== strpos( $user_agent, 'Android' ) ) { + if ( strpos( $user_agent, 'Kindle' ) ) { + $data['platform'] = 'Fire OS'; + } else { + $data['platform'] = 'Android'; + } + } + // Normalize Windows Phone OS name when "OS" is omitted. + elseif ( 'Windows Phone' === $data['platform'] ) { + $data['platform'] = 'Windows Phone OS'; + } + // Standardize Symbian OS name. + elseif ( + in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) + || + ! empty( $tokens['SymbianOS'] ) + || + ! empty( $tokens['Symbian'] ) + ) { + if ( ! in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) ) { + unset( $tokens['SymbianOS'] ); + unset( $tokens['Symbian'] ); + } + $data['platform'] = 'Symbian'; + } + // Generically detect some mobile devices. + elseif ( + ! $data['platform'] + && + preg_match( '/BlackBerry|Nokia|SonyEricsson/', $user_agent, $matches ) + ) { + $data['platform'] = 'Mobile'; + $mobile_device = $matches[0]; + } + + // Flag known mobile platforms as mobile. + if ( in_array( $data['platform'], [ 'Android', 'Fire OS', 'iPad', 'iPhone', 'Mobile', 'PlayBook', 'RIM Tablet OS', 'Symbian', 'Windows Phone OS' ] ) ) { + $data['mobile'] = true; + } + + // If Version/x.x.x was specified in UA string store it and ignore it + if ( ! empty( $tokens['Version'] ) ) { + $version = $tokens['Version']; + unset( $tokens['Version'] ); + } + + $explicit_tokens = get_explicit_browser_tokens(); + + // No indentifiers provided + if ( ! $tokens ) { + if ( 'BlackBerry' === $mobile_device ) { + $data['name'] = 'BlackBerry Browser'; + } else { + $data['name'] = 'unknown'; + } + } + // Explicitly identified browser (info defined above in $explicit_tokens). + elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { + $token = reset( $found ); + + $data['name'] = $explicit_tokens[ $token ]['name'] ?? $token; + $data['version'] = $tokens[ $token ]; + if ( empty( $explicit_tokens[ $token ]['use_version'] ) ) { + $version = ''; + } + if ( ! empty( $explicit_tokens[ $token ]['mobile'] ) ) { + $data['mobile'] = true; + } + if ( ! empty( $explicit_tokens[ $token ]['platform'] ) ) { + $data['platform'] = $explicit_tokens[ $token ]['platform']; + } + } + // Puffin + elseif ( ! empty( $tokens['Puffin'] ) ) { + $data['name'] = 'Puffin'; + $data['version'] = $tokens['Puffin']; + $version = ''; + // If not an already-identified mobile platform, set it as such. + if ( ! $data['mobile'] ) { + $data['mobile'] = true; + $data['platform'] = ''; + } + } + // Trident (Internet Explorer) + elseif ( ! empty( $tokens['Trident'] ) ) { + // IE 8-10 more reliably report version via Trident token than MSIE token. + // IE 11 uses Trident token without an MSIE token. + // https://msdn.microsoft.com/library/hh869301(v=vs.85).aspx + $data['name'] = 'Internet Explorer'; + $trident_ie_mapping = [ + '4.0' => '8.0', + '5.0' => '9.0', + '6.0' => '10.0', + '7.0' => '11.0', + ]; + $ver = $tokens['Trident']; + $data['version'] = $trident_ie_mapping[ $ver ] ?? $ver; + } + // Internet Explorer (pre v8.0) + elseif ( ! empty( $tokens['MSIE'] ) ) { + $data['name'] = 'Internet Explorer'; + $data['version'] = $tokens['MSIE']; + } + // AppleWebKit-emulating browsers + elseif ( ! empty( $tokens['AppleWebKit'] ) ) { + if ( ! empty( $tokens['Mobile Safari'] ) ) { + if ( ! empty( $tokens['Chrome'] ) ) { + $data['name'] = 'Chrome'; + $version = $tokens['Chrome']; + } elseif ( 'Android' === $data['platform'] ) { + $data['name'] = 'Android Browser'; + } elseif ( 'Fire OS' === $data['platform'] ) { + $data['name'] = 'Kindle Browser'; + } elseif ( false !== strpos( $user_agent, 'BlackBerry' ) || false !== strpos( $user_agent, 'BB10' ) ) { + $data['name'] = 'BlackBerry Browser'; + $data['mobile'] = true; + + if ( false !== stripos( $user_agent, 'BB10' ) ) { + $tokens['Mobile Safari'] = ''; + $version = ''; + } + } else { + $data['name'] = 'Mobile Safari'; + } + } elseif ( ! empty( $tokens['Chrome'] ) ) { + $data['name'] = 'Chrome'; + $version = ''; + } elseif ( ! empty( $data['platform'] ) && 'PlayBook' == $data['platform'] ) { + $data['name'] = 'PlayBook'; + } elseif ( ! empty( $tokens['Safari'] ) ) { + if ( 'Android' === $data['platform'] ) { + $data['name'] = 'Android Browser'; + } elseif ( 'Symbian' === $data['platform'] ) { + $data['name'] = 'Nokia Browser'; + $tokens['Safari'] = ''; + } else { + $data['name'] = 'Safari'; + } + } else { + $data['name'] = 'unknown'; + $tokens['AppleWebKit'] = ''; + $version = ''; + } + $data['version'] = $tokens[ $data['name'] ] ?? ''; + } + // Fall back to whatever is being reported. + else { + $ordered_tokens = array_reverse( $tokens ); + $data['version'] = reset( $ordered_tokens ); + $data['name'] = key( $ordered_tokens ); + } + + // Set the platform for Amazon-related browsers. + if ( in_array( $data['name'], [ 'Amazon Silk', 'Kindle Browser' ] ) ) { + $data['platform'] = 'Fire OS'; + $data['mobile'] = true; + } + + // If Version/x.x.x was specified in UA string + if ( ! empty( $version ) ) { + $data['version'] = $version; + } + + if ( $data['mobile'] ) { + // Generically set "Mobile" as the platform if a platform hasn't been set. + if ( ! $data['platform'] ) { + $data['platform'] = 'Mobile'; + } + + // Don't fetch additional browser data for mobile platform browsers at this time. + return $data; + } + + $browser_data = get_browser_data( $data['name'] ); + $data['update_url'] = $browser_data ? $browser_data->url : ''; + $data['current_version'] = get_browser_version_from_name( $data['name'] ); + $data['upgrade'] = ( ! empty( $data['current_version'] ) && version_compare( $data['version'], $data['current_version'], '<' ) ); + + if ( 'Internet Explorer' === $data['name'] ) { + $data['insecure'] = true; + $data['upgrade'] = true; + } elseif ( 'Firefox' === $data['name'] && version_compare( $data['version'], '52', '<' ) ) { + $data['insecure'] = true; + } elseif ( 'Opera' === $data['name'] && version_compare( $data['version'], '12.18', '<' ) ) { + $data['insecure'] = true; + } elseif ( 'Safari' === $data['name'] && version_compare( $data['version'], '10', '<' ) ) { + $data['insecure'] = true; + } + + return $data; +} + +/** + * Returns the current version for the given browser. + * + * @param string $name The name of the browser. + * @return string The version for the browser or an empty string if an + * unknown browser. + */ +function get_browser_version_from_name( $name ) { + $versions = get_browser_current_versions(); + + return isset( $versions[ $name ] ) ? $versions[ $name ] : ''; +} From c81634f402bb7d14b9d05deef295fd6c63fc540e Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 6 Feb 2026 19:37:07 -0600 Subject: [PATCH 2/6] PHPCS Issues from API code Signed-off-by: Joe Dolson --- inc/version-check/namespace.php | 154 ++++++++++++++++---------------- 1 file changed, 75 insertions(+), 79 deletions(-) diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index ec8d90e3..5657a447 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -90,15 +90,15 @@ function get_browser_check_response( string $agent ) { $data = parse_user_agent( $agent ); $default_data = [ - 'platform' => _x( 'your platform', 'operating system check', 'fair' ), - 'name' => _x( 'your browser', 'browser version check', 'fair' ), - 'version' => '', + 'platform' => _x( 'your platform', 'operating system check', 'fair' ), + 'name' => _x( 'your browser', 'browser version check', 'fair' ), + 'version' => '', 'current_version' => '', - 'upgrade' => ! $supported, - 'insecure' => ! $supported, - 'update_url' => 'https://browsehappy.com/', - 'img_src' => '', - 'img_src_ssl' => '', + 'upgrade' => ! $supported, + 'insecure' => ! $supported, + 'update_url' => 'https://browsehappy.com/', + 'img_src' => '', + 'img_src_ssl' => '', ]; $data = array_merge( $default_data, $data ); @@ -278,7 +278,7 @@ function get_server_check_response( string $version ) { */ function get_browser_current_versions() { return [ - 'Chrome' => '18', // Lowest version at the moment (mobile) + 'Chrome' => '18', // Lowest version at the moment (mobile). 'Firefox' => '56', 'Microsoft Edge' => '15.15063', 'Opera' => '12.18', @@ -375,62 +375,62 @@ function get_browser_data( $browser = false ) { */ function get_explicit_browser_tokens() { return [ - 'Camino' => [], - 'Chromium' => [], - 'Edge' => [ - 'name' => 'Microsoft Edge', + 'Camino' => [], + 'Chromium' => [], + 'Edge' => [ + 'name' => 'Microsoft Edge', ], - 'Kindle' => [ + 'Kindle' => [ 'name' => 'Kindle Browser', 'use_version' => true, ], - 'Konqueror' => [], - 'konqueror' => [ - 'name' => 'Konqueror', + 'Konqueror' => [], + 'konqueror' => [ + 'name'=> 'Konqueror', ], - 'NokiaBrowser' => [ - 'name' => 'Nokia Browser', - 'mobile' => true, + 'NokiaBrowser' => [ + 'name' => 'Nokia Browser', + 'mobile' => true, ], - 'Opera Mini' => [ // Must be before 'Opera' + 'Opera Mini' => [ // Must be before 'Opera' 'mobile' => true, 'use_version' => true, ], - 'Opera' => [ + 'Opera' => [ 'use_version' => true, ], - 'OPR' => [ + 'OPR' => [ 'name' => 'Opera', 'use_version' => true, ], - 'PaleMoon' => [ - 'name' => 'Pale Moon', + 'PaleMoon' => [ + 'name' => 'Pale Moon', ], - 'QQBrowser' => [ - 'name' => 'QQ Browser', + 'QQBrowser' => [ + 'name' => 'QQ Browser', ], - 'RockMelt' => [], - 'SamsungBrowser' => [ - 'name' => 'Samsung Browser', + 'RockMelt' => [], + 'SamsungBrowser' => [ + 'name' => 'Samsung Browser', ], - 'SeaMonkey' => [], - 'Silk' => [ - 'name' => 'Amazon Silk', + 'SeaMonkey' => [], + 'Silk' => [ + 'name'=> 'Amazon Silk', ], - 'S40OviBrowser' => [ - 'name' => 'Ovi Browser', - 'mobile' => true, - 'platform' => 'Symbian', + 'S40OviBrowser' => [ + 'name' => 'Ovi Browser', + 'mobile' => true, + 'platform' => 'Symbian', ], - 'UCBrowser' => [ // Must be before 'UCWEB' - 'name' => 'UC Browser', + 'UCBrowser' => [ // Must be before 'UCWEB' + 'name' => 'UC Browser', ], - 'UCWEB' => [ - 'name' => 'UC Browser', + 'UCWEB' => [ + 'name' => 'UC Browser', ], - 'Vivaldi' => [], - 'IEMobile' => [ // Keep last just in case - 'name' => 'Internet Explorer Mobile', + 'Vivaldi' => [], + 'IEMobile' => [ // Keep last just in case + 'name' => 'Internet Explorer Mobile', ], ]; } @@ -471,20 +471,20 @@ function parse_user_agent( $user_agent ) { // Identify platform/OS in user-agent string. if ( preg_match( - '/(?P' // Capture subpattern matches into 'platform' array - . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens - . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens - . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens + '/(?P' // Capture subpattern matches into 'platform' array. + . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. + . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. + . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. . ')' . '(?:' - . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers + . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. . ')*' . '(?:' - . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64) - . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number + . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). + . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. . ')*' - . '(;|\))' // Ending in a semi-colon or close parenthesis - . '/im', // Case insensitive, multiline + . '(;|\))' // Ending in a semi-colon or close parenthesis. + . '/im', // Case insensitive, multiline. $user_agent, $regs ) ) { @@ -493,22 +493,22 @@ function parse_user_agent( $user_agent ) { // Find tokens of interest in user-agent string. preg_match_all( - '%(?P' // Capture subpattern matches into the 'name' array - . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens - . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens - . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens - . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens - . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens - . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens - . '|Version' // Version token + '%(?P' // Capture subpattern matches into the 'name' array. + . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens. + . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens. + . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens. + . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens. + . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens. + . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens. + . '|Version' // Version token. . ')' . '(?:' - . '[/ ]' // Forward slash or space + . '[/ ]' // Forward slash or space. . ')' - . '(?P' // Capture subpattern matches into 'version' array - . '[0-9.]+' // One or more numbers and/or decimal points + . '(?P' // Capture subpattern matches into 'version' array. + . '[0-9.]+' // One or more numbers and/or decimal points. . ')' - . '%im', // Case insensitive, multiline + . '%im', // Case insensitive, multiline. $user_agent, $result, PREG_PATTERN_ORDER @@ -600,12 +600,11 @@ function parse_user_agent( $user_agent ) { $data['mobile'] = true; $data['platform'] = ''; } - } - // Trident (Internet Explorer) - elseif ( ! empty( $tokens['Trident'] ) ) { + } elseif ( ! empty( $tokens['Trident'] ) ) { + // Trident (Internet Explorer). // IE 8-10 more reliably report version via Trident token than MSIE token. // IE 11 uses Trident token without an MSIE token. - // https://msdn.microsoft.com/library/hh869301(v=vs.85).aspx + // https://msdn.microsoft.com/library/hh869301(v=vs.85).aspx. $data['name'] = 'Internet Explorer'; $trident_ie_mapping = [ '4.0' => '8.0', @@ -615,14 +614,12 @@ function parse_user_agent( $user_agent ) { ]; $ver = $tokens['Trident']; $data['version'] = $trident_ie_mapping[ $ver ] ?? $ver; - } - // Internet Explorer (pre v8.0) - elseif ( ! empty( $tokens['MSIE'] ) ) { + } elseif ( ! empty( $tokens['MSIE'] ) ) { + // Internet Explorer (pre v8.0). $data['name'] = 'Internet Explorer'; $data['version'] = $tokens['MSIE']; - } - // AppleWebKit-emulating browsers - elseif ( ! empty( $tokens['AppleWebKit'] ) ) { + } elseif ( ! empty( $tokens['AppleWebKit'] ) ) { + // AppleWebKit-emulating browsers. if ( ! empty( $tokens['Mobile Safari'] ) ) { if ( ! empty( $tokens['Chrome'] ) ) { $data['name'] = 'Chrome'; @@ -662,9 +659,8 @@ function parse_user_agent( $user_agent ) { $version = ''; } $data['version'] = $tokens[ $data['name'] ] ?? ''; - } - // Fall back to whatever is being reported. - else { + } else { + // Fall back to whatever is being reported. $ordered_tokens = array_reverse( $tokens ); $data['version'] = reset( $ordered_tokens ); $data['name'] = key( $ordered_tokens ); @@ -676,7 +672,7 @@ function parse_user_agent( $user_agent ) { $data['mobile'] = true; } - // If Version/x.x.x was specified in UA string + // If Version/x.x.x was specified in UA string. if ( ! empty( $version ) ) { $data['version'] = $version; } From 3035281ff94c84bd472fd29f82abb3b171203814 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 6 Feb 2026 19:43:59 -0600 Subject: [PATCH 3/6] More code sniffing changes Signed-off-by: Joe Dolson --- inc/version-check/namespace.php | 55 ++++++++++++--------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index 5657a447..5cf1f852 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -471,17 +471,17 @@ function parse_user_agent( $user_agent ) { // Identify platform/OS in user-agent string. if ( preg_match( - '/(?P' // Capture subpattern matches into 'platform' array. - . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. - . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. - . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. + '/(?P' // Capture subpattern matches into 'platform' array. + . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. + . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. + . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. . ')' . '(?:' - . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. + . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. . ')*' . '(?:' - . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). - . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. + . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). + . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. . ')*' . '(;|\))' // Ending in a semi-colon or close parenthesis. . '/im', // Case insensitive, multiline. @@ -493,7 +493,7 @@ function parse_user_agent( $user_agent ) { // Find tokens of interest in user-agent string. preg_match_all( - '%(?P' // Capture subpattern matches into the 'name' array. + '%(?P' // Capture subpattern matches into the 'name' array. . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens. . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens. . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens. @@ -524,31 +524,18 @@ function parse_user_agent( $user_agent ) { } else { $data['platform'] = 'Android'; } - } - // Normalize Windows Phone OS name when "OS" is omitted. - elseif ( 'Windows Phone' === $data['platform'] ) { + } elseif ( 'Windows Phone' === $data['platform'] ) { + // Normalize Windows Phone OS name when "OS" is omitted. $data['platform'] = 'Windows Phone OS'; - } - // Standardize Symbian OS name. - elseif ( - in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) - || - ! empty( $tokens['SymbianOS'] ) - || - ! empty( $tokens['Symbian'] ) - ) { + } elseif ( in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) || ! empty( $tokens['SymbianOS'] ) || ! empty( $tokens['Symbian'] ) ) { + // Standardize Symbian OS name. if ( ! in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) ) { unset( $tokens['SymbianOS'] ); unset( $tokens['Symbian'] ); } $data['platform'] = 'Symbian'; - } - // Generically detect some mobile devices. - elseif ( - ! $data['platform'] - && - preg_match( '/BlackBerry|Nokia|SonyEricsson/', $user_agent, $matches ) - ) { + } elseif ( ! $data['platform'] && preg_match( '/BlackBerry|Nokia|SonyEricsson/', $user_agent, $matches ) ) { + // Generically detect some mobile devices. $data['platform'] = 'Mobile'; $mobile_device = $matches[0]; } @@ -558,7 +545,7 @@ function parse_user_agent( $user_agent ) { $data['mobile'] = true; } - // If Version/x.x.x was specified in UA string store it and ignore it + // If Version/x.x.x was specified in UA string store it and ignore it. if ( ! empty( $tokens['Version'] ) ) { $version = $tokens['Version']; unset( $tokens['Version'] ); @@ -566,16 +553,15 @@ function parse_user_agent( $user_agent ) { $explicit_tokens = get_explicit_browser_tokens(); - // No indentifiers provided + // No indentifiers provided. if ( ! $tokens ) { if ( 'BlackBerry' === $mobile_device ) { $data['name'] = 'BlackBerry Browser'; } else { $data['name'] = 'unknown'; } - } - // Explicitly identified browser (info defined above in $explicit_tokens). - elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { + } elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { + // Explicitly identified browser (info defined above in $explicit_tokens). $token = reset( $found ); $data['name'] = $explicit_tokens[ $token ]['name'] ?? $token; @@ -589,9 +575,8 @@ function parse_user_agent( $user_agent ) { if ( ! empty( $explicit_tokens[ $token ]['platform'] ) ) { $data['platform'] = $explicit_tokens[ $token ]['platform']; } - } - // Puffin - elseif ( ! empty( $tokens['Puffin'] ) ) { + } elseif ( ! empty( $tokens['Puffin'] ) ) { + // Puffin $data['name'] = 'Puffin'; $data['version'] = $tokens['Puffin']; $version = ''; From f6209c8f41a18f5ce295386a010df322d4cdc0e5 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 6 Feb 2026 20:14:46 -0600 Subject: [PATCH 4/6] Indentation; separate pattern docs from patterns Signed-off-by: Joe Dolson --- inc/version-check/namespace.php | 93 ++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index 5cf1f852..f6179e51 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -90,15 +90,15 @@ function get_browser_check_response( string $agent ) { $data = parse_user_agent( $agent ); $default_data = [ - 'platform' => _x( 'your platform', 'operating system check', 'fair' ), - 'name' => _x( 'your browser', 'browser version check', 'fair' ), - 'version' => '', - 'current_version' => '', - 'upgrade' => ! $supported, - 'insecure' => ! $supported, - 'update_url' => 'https://browsehappy.com/', - 'img_src' => '', - 'img_src_ssl' => '', + 'platform' => _x( 'your platform', 'operating system check', 'fair' ), + 'name' => _x( 'your browser', 'browser version check', 'fair' ), + 'version' => '', + 'current_version' => '', + 'upgrade' => ! $supported, + 'insecure' => ! $supported, + 'update_url' => 'https://browsehappy.com/', + 'img_src' => '', + 'img_src_ssl' => '', ]; $data = array_merge( $default_data, $data ); @@ -374,7 +374,7 @@ function get_browser_data( $browser = false ) { * } */ function get_explicit_browser_tokens() { - return [ + return [ 'Camino' => [], 'Chromium' => [], 'Edge' => [ @@ -432,7 +432,7 @@ function get_explicit_browser_tokens() { 'IEMobile' => [ // Keep last just in case 'name' => 'Internet Explorer Mobile', ], - ]; + ]; } /** @@ -469,46 +469,53 @@ function parse_user_agent( $user_agent ) { ]; $mobile_device = ''; - // Identify platform/OS in user-agent string. + /** + * Identify platform/OS in user-agent string. + * '/(?P' // Capture subpattern matches into 'platform' array. + * . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. + * . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. + * . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. + * . ')' + * . '(?:' + * . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. + * . ')*' + * . '(?:' + * . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). + * . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. + * . ')*' + * . '(;|\))' // Ending in a semi-colon or close parenthesis. + * . '/im', // Case insensitive, multiline. + */ if ( preg_match( - '/(?P' // Capture subpattern matches into 'platform' array. - . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. - . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. - . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. - . ')' - . '(?:' - . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. - . ')*' - . '(?:' - . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). - . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. - . ')*' - . '(;|\))' // Ending in a semi-colon or close parenthesis. - . '/im', // Case insensitive, multiline. + '/(?PWindows Phone( OS)?|Symbian|SymbOS|Android|iPhone|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD|SunOS|RIM Tablet OS|PlayBook)(?: (NT|amd64|armv7l|zvav))*(?: [ix]?[0-9._]+(\-[0-9a-z\.\-]+)?)*(;|\))/im', $user_agent, $regs ) ) { $data['platform'] = $regs['platform']; } - // Find tokens of interest in user-agent string. + /** + * Find tokens of interest in user-agent string. + * + * '%(?P' // Capture subpattern matches into the 'name' array. + * . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens. + * . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens. + * . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens. + * . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens. + * . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens. + * . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens. + * . '|Version' // Version token. + * . ')' + * . '(?:' + * . '[/ ]' // Forward slash or space. + * . ')' + * . '(?P' // Capture subpattern matches into 'version' array. + * . '[0-9.]+' // One or more numbers and/or decimal points. + * . ')' + * . '%im', // Case insensitive, multiline. + */ preg_match_all( - '%(?P' // Capture subpattern matches into the 'name' array. - . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens. - . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens. - . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens. - . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens. - . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens. - . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens. - . '|Version' // Version token. - . ')' - . '(?:' - . '[/ ]' // Forward slash or space. - . ')' - . '(?P' // Capture subpattern matches into 'version' array. - . '[0-9.]+' // One or more numbers and/or decimal points. - . ')' - . '%im', // Case insensitive, multiline. + '%(?POpera Mini|Opera|OPR|Edge|UCBrowser|UCWEB|QQBrowser|SymbianOS|Symbian|S40OviBrowser|Trident|Silk|Konqueror|PaleMoon|Puffin|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile|Version)(?:[/ ])(?P[0-9.]+)%im', $user_agent, $result, PREG_PATTERN_ORDER From c40151a525fade4d38e2e14b1f3efd50feb6e9bc Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 6 Feb 2026 20:18:07 -0600 Subject: [PATCH 5/6] Overlooked leading tabs in doc Signed-off-by: Joe Dolson --- inc/version-check/namespace.php | 72 ++++++++++++++++----------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index f6179e51..6ebc183f 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -386,13 +386,13 @@ function get_explicit_browser_tokens() { ], 'Konqueror' => [], 'konqueror' => [ - 'name'=> 'Konqueror', + 'name' => 'Konqueror', ], 'NokiaBrowser' => [ 'name' => 'Nokia Browser', 'mobile' => true, ], - 'Opera Mini' => [ // Must be before 'Opera' + 'Opera Mini' => [ // Must be before 'Opera'. 'mobile' => true, 'use_version' => true, ], @@ -415,21 +415,21 @@ function get_explicit_browser_tokens() { ], 'SeaMonkey' => [], 'Silk' => [ - 'name'=> 'Amazon Silk', + 'name' => 'Amazon Silk', ], 'S40OviBrowser' => [ 'name' => 'Ovi Browser', 'mobile' => true, 'platform' => 'Symbian', ], - 'UCBrowser' => [ // Must be before 'UCWEB' + 'UCBrowser' => [ // Must be before 'UCWEB'. 'name' => 'UC Browser', ], 'UCWEB' => [ 'name' => 'UC Browser', ], 'Vivaldi' => [], - 'IEMobile' => [ // Keep last just in case + 'IEMobile' => [ // Keep last just in case. 'name' => 'Internet Explorer Mobile', ], ]; @@ -471,20 +471,20 @@ function parse_user_agent( $user_agent ) { /** * Identify platform/OS in user-agent string. - * '/(?P' // Capture subpattern matches into 'platform' array. - * . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. - * . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. - * . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. - * . ')' - * . '(?:' - * . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. - * . ')*' - * . '(?:' - * . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). - * . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. - * . ')*' - * . '(;|\))' // Ending in a semi-colon or close parenthesis. - * . '/im', // Case insensitive, multiline. + * '/(?P' // Capture subpattern matches into 'platform' array. + * . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. + * . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. + * . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. + * . ')' + * . '(?:' + * . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. + * . ')*' + * . '(?:' + * . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). + * . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. + * . ')*' + * . '(;|\))' // Ending in a semi-colon or close parenthesis. + * . '/im', // Case insensitive, multiline. */ if ( preg_match( '/(?PWindows Phone( OS)?|Symbian|SymbOS|Android|iPhone|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD|SunOS|RIM Tablet OS|PlayBook)(?: (NT|amd64|armv7l|zvav))*(?: [ix]?[0-9._]+(\-[0-9a-z\.\-]+)?)*(;|\))/im', @@ -497,22 +497,22 @@ function parse_user_agent( $user_agent ) { /** * Find tokens of interest in user-agent string. * - * '%(?P' // Capture subpattern matches into the 'name' array. - * . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens. - * . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens. - * . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens. - * . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens. - * . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens. - * . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens. - * . '|Version' // Version token. - * . ')' - * . '(?:' - * . '[/ ]' // Forward slash or space. - * . ')' - * . '(?P' // Capture subpattern matches into 'version' array. - * . '[0-9.]+' // One or more numbers and/or decimal points. - * . ')' - * . '%im', // Case insensitive, multiline. + * '%(?P' // Capture subpattern matches into the 'name' array. + * . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens. + * . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens. + * . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens. + * . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens. + * . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens. + * . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens. + * . '|Version' // Version token. + * . ')' + * . '(?:' + * . '[/ ]' // Forward slash or space. + * . ')' + * . '(?P' // Capture subpattern matches into 'version' array. + * . '[0-9.]+' // One or more numbers and/or decimal points. + * . ')' + * . '%im', // Case insensitive, multiline. */ preg_match_all( '%(?POpera Mini|Opera|OPR|Edge|UCBrowser|UCWEB|QQBrowser|SymbianOS|Symbian|S40OviBrowser|Trident|Silk|Konqueror|PaleMoon|Puffin|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile|Version)(?:[/ ])(?P[0-9.]+)%im', @@ -583,7 +583,7 @@ function parse_user_agent( $user_agent ) { $data['platform'] = $explicit_tokens[ $token ]['platform']; } } elseif ( ! empty( $tokens['Puffin'] ) ) { - // Puffin + // Puffin. $data['name'] = 'Puffin'; $data['version'] = $tokens['Puffin']; $version = ''; From 2d778b597d76906bdeaee98658e0cabb5e64872e Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 6 Feb 2026 20:20:15 -0600 Subject: [PATCH 6/6] I'm going to ignore that. Signed-off-by: Joe Dolson --- inc/version-check/namespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index 6ebc183f..090a53c9 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -567,7 +567,7 @@ function parse_user_agent( $user_agent ) { } else { $data['name'] = 'unknown'; } - } elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { + } elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure // Explicitly identified browser (info defined above in $explicit_tokens). $token = reset( $found );