Search code examples
phpregexversionuser-agentpcre

PHP: Browser version number user-agent with Version/x.x.x (Safari & Opera)


I wrote a simple class to check for user agents to display a warning for incompatible browsers. I'm doing this server side, I know it's possible client side.

Okey first off, I'm not much good for writing regexes..

I wrote a regex that searches for lower case browser names followed by the version number. I do a foreach() with an array something like this:

<?php
    $browsers = Array('msie','chrome','safari','firefox','opera');    

    foreach($browsers as $i => $browser)  
    {
        $regex = "#({$browser})[/ ]([0-9.]*)#i";

        if(preg_match($regex, $useragent, $matches))
        {
            echo "Browser: \"{$matches[0]}\", version: \"{$matches[1]}\"";
        }
    }
?>

This would yield: Browser: "Firefox", version "23.0.6".

I found this works for Firefox, MS IE, and older versions of Opera. However some browsers like Safari and the newer versions of Opera have a different user-agent string that contains Version/x.x.x, which is

Just to give you the an idea here are 3 user-agent strings and what I need is highlighted.

  1. Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/536.30.1 (KHTML, like Gecko) Version/6.0.5 Safari/536.30.1
  2. Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)
  3. Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14

So in each of these the following human logic correct:

  • If there is a Version/x.x.x in the string that is the version number.
  • If there isn't then Browsername/x.x.x is the version number.

Also if you look at the 1st and last user-agent string above, you can see the Version can come before or after the browser name.

Can somebody help me to make a regex to be used with preg_match()? Do I need to use a conditional statement or can I search for optional groupings? I'm a bit confused..

Thanks!

Edit 17-09-2013: I forgot to mention, I don't want to use get_browser(), it uses a huge library to detect the browsers capabilities etc. I only need a short "whitelist" of browsers that should take a few milliseconds rather than a few hundred ms to read the browse cap.ini files.. Otherwise George's answer would've been the answer..


Solution

  • Ended up doing it a little differently since I had some trouble with some browsers with Remus' answer.

    <?php
    
    function get_useragent_info($ua)
    {
        $ua = is_null($ua) ? $_SERVER['HTTP_USER_AGENT'] : $ua;
        // Enumerate all common platforms, this is usually placed in braces (order is important! First come first serve..)
        $platforms = "Windows|iPad|iPhone|Macintosh|Android|BlackBerry";
    
        // All browsers except MSIE/Trident and.. 
        // NOT for browsers that use this syntax: Version/0.xx Browsername  
        $browsers = "Firefox|Chrome"; 
    
        // Specifically for browsers that use this syntax: Version/0.xx Browername  
        $browsers_v = "Safari|Mobile"; // Mobile is mentioned in Android and BlackBerry UA's
    
        // Fill in your most common engines..
        $engines = "Gecko|Trident|Webkit|Presto";
    
        // Regex the crap out of the user agent, making multiple selections and.. 
        $regex_pat = "/((Mozilla)\/[\d\.]+|(Opera)\/[\d\.]+)\s\(.*?((MSIE)\s([\d\.]+).*?(Windows)|({$platforms})).*?\s.*?({$engines})[\/\s]+[\d\.]+(\;\srv\:([\d\.]+)|.*?).*?(Version[\/\s]([\d\.]+)(.*?({$browsers_v})|$)|(({$browsers})[\/\s]+([\d\.]+))|$).*/i";
    
        // .. placing them in this order, delimited by |
        $replace_pat = '$7$8|$2$3|$9|${17}${15}$5$3|${18}${13}$6${11}';
    
        // Run the preg_replace .. and explode on |
        $ua_array = explode("|",preg_replace($regex_pat, $replace_pat, $ua, PREG_PATTERN_ORDER));
    
        if (count($ua_array)>1)
        {
            $return['platform']  = $ua_array[0];  // Windows / iPad / MacOS / BlackBerry
            $return['type']      = $ua_array[1];  // Mozilla / Opera etc.
            $return['renderer']  = $ua_array[2];  // WebKit / Presto / Trident / Gecko etc.
            $return['browser']   = $ua_array[3];  // Chrome / Safari / MSIE / Firefox
    
            /* 
               Not necessary but this will filter out Chromes ridiculously long version
               numbers 31.0.1234.122 becomes 31.0, while a "normal" 3 digit version number 
               like 10.2.1 would stay 10.2.1, 11.0 stays 11.0. Non-match stays what it is.
            */
    
            if (preg_match("/^[\d]+\.[\d]+(?:\.[\d]{0,2}$)?/",$ua_array[4],$matches))
            {
                $return['version'] = $matches[0];     
            }
            else
            {
                $return['version'] = $ua_array[4];
            }
        }
        else
        {
            /*
               Unknown browser.. 
               This could be a deal breaker for you but I use this to actually keep old
               browsers out of my application, users are told to download a compatible
               browser (99% of modern browsers are compatible. You can also ignore my error
               but then there is no guarantee that the application will work and thus
               no need to report debugging data.
             */
    
            return false;
        }
    
        // Replace some browsernames e.g. MSIE -> Internet Explorer
        switch(strtolower($return['browser']))
        {
            case "msie":
            case "trident":
                $return['browser'] = "Internet Explorer";
                break;
            case "": // IE 11 is a steamy turd (thanks Microsoft...)
                if (strtolower($return['renderer']) == "trident")
                {
                    $return['browser'] = "Internet Explorer";
                }
            break;
        }
    
        switch(strtolower($return['platform']))
        {
            case "android":    // These browsers claim to be Safari but are BB Mobile 
            case "blackberry": // and Android Mobile
                if ($return['browser'] =="Safari" || $return['browser'] == "Mobile" || $return['browser'] == "")
                {
                    $return['browser'] = "{$return['platform']} mobile";
                }
                break;
        }
    
        return $return;
    } // End of Function
    ?>