Take the 2-minute tour ×
Webmasters Stack Exchange is a question and answer site for pro webmasters. It's 100% free, no registration required.

My client has asked me to implement a browser detection system for the admin login with the following criteria, allow these:

  • Internet Explorer 8 or newer
  • Firefox 3.6 or newer
  • Safari 5 or newer for Mac only

And everything else should be blocked. They want me to implement a page telling the user what browser they need to upgrade/switch to in order to access the CMS.

Basically I need to know the best way to detect these browsers with PHP, distinct from any other browsers, and I've read that browser sniffing per se is not a good idea.

The CMS is WordPress but this is not a WordPress question (FYI I am a moderator on the WordPress Answers site.) Once I figure out the right technique to detect the browser I'm fully capable to make WordPress react as my client wants, I just need to know what the best ways are with PHP (or worse case jQuery, but I much prefer to do on the server) to figure how what works and what doesn't.

Please understand that "Don't do it" is not an acceptable answer for this question. I know this client too well and when they ask me to implement something I need to do it (they are a really good client so I'm happy to do what they ask.)

Thanks in advance for your expertise.

UPDATE

This requirement is for the admin console, not the public site; we'll support everything on the public site but we've heavily modified the WordPress admin console and we only want to have to test on the main three (3) browsers. So feature detection is not really relevant here; this is a testing and support requirement, not a requirement for supporting specific features.

And the project does not have the funding to support all browsers; it's just not realistic when there are probably only going to be 25 or 50 users of the admin console in total.

UPDATE #2

For the benefit of anyone with the same requirements here's the solution I came up with:

$min_versions = array(
  'safari'  => 5,
  'firefox' => 3.6,
  'msie'    => 8,
);
$good_browser = false;
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
if (preg_match("#applewebkit/.*?version/([0-9]+)\..*?safari/#", $user_agent, $match)) {
  $good_browser = intval($match[1])>=self::$min_versions['safari'];
} else {
  foreach(array('firefox','msie') as $browser) {
    if (preg_match("#{$browser}/([0-9]+)\.#", $user_agent, $match)) {
      $good_browser = intval($match[1])>=self::$min_versions[$browser];
      break;
    }
  }
}
if (!$good_browser) {
  // Do redirect to an upgrade page
}

And this is the solution packaged as a standalone file designed to be included in a WordPress plugin or by the functions.php file of a WordPress theme:

<?php
/* my-clients-browser-detect.php */

if (!class_exists('MyClients_BrowserDetector')) {
  class MyClients_BrowserDetector {
    static $min_versions = array(
      'safari'  => 5,
      'firefox' => 3.6,
      'msie'    => 8,
    );
    static function admin_init() {
      $good_browser = false;
      $user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
      if (preg_match("#applewebkit/.*?version/([0-9]+)\..*?safari/#", $user_agent, $match)) {
        $good_browser = intval($match[1])>=self::$min_versions['safari'];
      } else {
        foreach(array('firefox','msie') as $browser) {
          if (preg_match("#{$browser}/([0-9]+)\.#", $user_agent, $match)) {
            $good_browser = intval($match[1])>=self::$min_versions[$browser];
            break;
          }
        }
      }
      if (!$good_browser) {
        $pages = self::get_pages('page-upgrade-browser.php');
        if (count($pages)==0) {
          echo "<p>Please upgrade your browser to one of the following:</p><ul>";
          foreach(self::$min_versions as $browser => $version) {
            echo "<li>{$browser}: {$version}</li>";
          }
          echo "</ul>";
          exit;
        }
        $permalink = get_page_link(get_post($pages[0])->ID);
        wp_safe_redirect($permalink,302);
      }
    }
    static function get_pages($template) {
      global $wpdb;
      $sql = $wpdb->prepare("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_wp_page_template' AND meta_value='%s'",$template);
      $pages = $wpdb->get_col($sql);
      foreach($pages as $index => $page_id)
        $pages[$index] = get_post($page_id);
      return $pages;
    }
    static function on_load() {
      add_action('admin_init', array(__CLASS__,'admin_init'));
    }
  }
  MyClients_BrowserDetector::on_load();
}

As several people have mentioned feature detection is usually considered a better approach but for this use-case we really didn't have specific features we cared about, we had specific named browsers we cared about so feature detection wasn't a viable option. FYI, the client knows that we may have to revise this script in the future if we can false negatives or false positives.

-Mike

share|improve this question
1  
Based on your list of accepted browser, it seems really strange to exclude Chrome and I'd be very surprised if the latest version of Opera can not handle something that the above browsers can. –  Kris Jan 14 '11 at 10:47
    
Well I'm going to add "don't do it" ;) You should explain to your client that Wordpress is cross-browser. There may be a case for asking IE6-7 users to upgrade, but not Firefox 3.5, Chrome or Opera. –  DisgruntledGoat Jan 14 '11 at 12:35
    
@Kris - It's the list the client gave me; they know their clients pretty well (mostly paralegals) and only want to support a small set of common browsers in the admin console. –  MikeSchinkel Jan 14 '11 at 12:43
    
@DisgruntledGoat - Not helpful. WordPress is cross-browser, but we've modified WordPress' admin console heavily. (I refer back to my question: 'Please understand that "Don't do it" is not an acceptable answer for this question.') –  MikeSchinkel Jan 14 '11 at 12:44
    
@Mike My point was that Chrome (being Webkit under the hood like Safari) will almost certainly work just as well. And Opera should also work on a site that works in the three browsers mentioned. While it is common for clients to specify something like this, it is up to guys like us to educate our clients. At most the users of recent versions of Chrome and Opera should get a warning that their browsers are not supported. Excluding them is bad form. –  Kris Jan 14 '11 at 12:46
show 4 more comments

4 Answers 4

up vote 3 down vote accepted

The unique way I know to do that on server side is to analyze the User Agent HTTP Header.

I know that using the get_browser function may be useful, or may be a pain in the a**. Because it depends on external files that can be outdated.

The safe solution? None actually, but you can shortcut the browscap.ini file lookup to faster, safer solution (but will require more maintenance).

Check the list of http user agents, and just match the string you need against the HTTP UAs you receive.

For example, IE 8 always send

Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)

or

Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)

Firefox always send:

Mozilla/5.0 (xxx; xxx; xxxxxxx; xx-xx; rv:x.x.x.x) Gecko/2xxxxxxx Firefox/3.6.x

Sarafi sends:

Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/xxx.x (KHTML like Gecko) Safari/5xx.x

This is what you have. There is no solution further than this. Even javascript solutions use to work with these pre-defined values to provide platform and browser in the client side, hence these are the values javascript engine has to process.

Thinking further, the main risks you inccur is a client trying to use IE7, IE9 beta, Firefox 3.5, Google Chrome and Opera 10+. Few people, if any actually, would try to access you page using FF 2.0 with HTTP User Agent Switcher or some odd browser.

I know, this solution is far away from being perfect, or reliable enough, but you need to consider the scenario it is inserted, and the chances of someone use Dillo, IE 5.5 or Safari 2.0....

share|improve this answer
    
Actually, your solution seems perfectly reasonable considering his purpose. It doesn't need to be 100% accurate since his client just needs to get a general browser distribution of their user base. So if someone uses browser cloaking or even has a rare browser with an obscure or spoofed user-agent string, it will still be fine. –  Lèse majesté Jan 14 '11 at 15:16
    
Thanks for your detailed answer. Yes, after research it does seem your answer is the best option. I'll post what I came up with as an update to the answer as well. –  MikeSchinkel Jan 16 '11 at 18:07
    
@Mike Schinkel - elegant code for solution. I'm glad to clarify some points for you. Well, just don't forget to conduce some tests on your script with the most obvious desired and undesired browsers. FF 3, 3.5, 3.6, IE 6, IE 7, IE 8, Safari 4 and Safari 5 should be enough for start. Perhaps FF 4 beta and IE 9 beta. –  Dave Jan 17 '11 at 21:57
    
Thanks for the tips! –  MikeSchinkel Jan 18 '11 at 0:35
add comment

$_SERVER['HTTP_USER_AGENT'] contains the string you seek - how you parse it is up to you... user-agents.org provides examples of common contents.

Apparently this problem comes up often enough that there's a function - get_browser() - which specifically addresses it so you're looking for something like this:

$user_agent = get_browser( $_SERVER['HTTP_USER_AGENT'] );

... and from there you can apply appropriate checks against the returned object.


Update:

To clarify why this is ultimately a bad idea: you have no control over when Microsoft, Mozilla/Firefox, and Safari decide to change their user agent strings - there is no magic bullet regular expression which will withstand arbitrary changes, there is no database which will not eventually be obsolete, et cetera.

share|improve this answer
    
@danlefree - Thanks for the suggestion, but I should probably have said that I know how to get the 'HTTP_USER_AGENT' string; what I'm trying to understand is a reliable way to determine what the string contains. For a humorous explanation of my dilemma, read: webaim.org/blog/user-agent-string-history –  MikeSchinkel Jan 14 '11 at 7:05
    
What do you mean by "a reliable way to determine what the string contains"? Usually just parsing the variable is pretty reliable. If you want a fool-proof way to identify a browser and all future browsers, there's no way to do it. But that's just one of the many reasons why browser sniffing is a bad idea. If you value that client, you should do what's best for them and go with feature detection. –  Lèse majesté Jan 14 '11 at 7:28
    
@Lèse majesté - Here's a list of user agent strings so it's not straight forward: zytrax.com/tech/web/browser_ids.htm And I am doing "what's best for the client" because feature detection is not applicable for this use case; they want to know what browsers they need to test with, not which features to turn on or turn off. This is not for the public site, this is for the admin console and it's their clients whose browsers will be tested. They want to be able to say "We support these browsers: X, Y and Z." –  MikeSchinkel Jan 14 '11 at 12:37
    
@daaneifree - Thanks for the update but get_browser() isn't a really good option because it depends on an obsoleteable browscap.ini and according to php.net/manual/en/function.get-browser.php "Keep in mind that get_browser(); really slows down your application. It takes about 22 ms to execute on an idle server with Ubuntu Linux, Apache 2, PHP 5.1.3." –  MikeSchinkel Jan 14 '11 at 12:41
1  
@Mike - It would be foolish to check the browser on every request against an authenticated interface (just check at authentication) but I think you're a little too optimistic thinking there is a solution to this problem which isn't going to be obsolete eventually... this is exactly why browser sniffing == bad idea. –  danlefree Jan 14 '11 at 14:17
show 3 more comments

If what you're trying to do is to identify the dominant browsers being used so you can test with those specific browsers, then recording user-agent strings should be enough. Sure, there will be an error margin due to spoofed user-agent strings, but you don't need total accuracy for your application. What difference does it make if IE6 represents 10% of your userbase or 10.5%? If 1-2% of users have obscure or unidentifiable browsers, that's probably not enough to shift the browsers you'll officially support.

And there's a reason why many browsers lie about their identities or try to disguise themselves as someone else. It's precisely because of this kind of preemptive exclusion that they need to do this. If there's some obscure browser out there that uses the same Trident engine as IE8, then why should they be turned away from sites or served a more basic version of the page just because the web developer has never heard of that particular browser? If it can render the page just as well as IE8, it's not your place to tell the user that their browser is insufficient and that they need to "upgrade" to some other browser. That's why feature detection is preferred (and is still completely relevant to your situation). It avoids arbitrarily treating unknown/future browsers as second-class citizens and it sidesteps the problems with browser detection.

Lastly, given that you now say there are only 50 users total that will be using this admin area, there is all the more reason to just identify browser usage patterns via user research. Just log the user-agent string on sign-on or via a test page emailed to users and then analyze the logs manually and you can quickly identify the top 3 browsers. You can re-assess the browser distribution every so often so you can keep up with new browsers.

Though given that you're only going to support the top 3 browsers, you can probably just look up the stats on the W3C site, since they're basically the same for most sites, just in different order for different demographics.

share|improve this answer
    
On the open web I agree that it's not a good idea to require specific browsers, and I'm aware of history of user-agent spoofing: webaim.org/blog/user-agent-string-history For this use-case we believe we have a viable approach. As for viewing the logs well might be work for an existing site on the open web but this is for a software product that today has zero users and thus no logs except for those that came from me and my client's testing. We can't know in advance what browsers people will try to use. After this has been in use a while we can revisit. –  MikeSchinkel Jan 16 '11 at 18:53
    
As for feature detection, one final time, we don't have specific features we know to test for. Identifying the features that we'd need to test for would take for more time than this project can afford to budget. Picking three main browsers and promising clients to support them is financially much more viable in my clients view. –  MikeSchinkel Jan 16 '11 at 18:59
add comment

Adapt PPK's JavaScript-based browser detection script

Note that most browsers have ways (natively or through add-ons) to report their user-agent as something else. So there may not be a 100% foolproof way to detect browsers.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.