As part of the accessibility module's functionality was to detect the type of device browsing the site I decided to make a stand alone version of the detection part of the script. This can then be used in various ways, for example to redirect your user to a suitable version, or to render the correct content on your site relevant to the users device.
The script comes in 2 parts. browser.inc is just a simple text file that contains a list of the first 4 characters of the user_agent each displayed on a new line. This file is then searched and if a match is found to the string the device can be said to be a mobile.
The main file, accessibility.php does the main work. This file should be included in any pages you need detection available on. You can then call the 2 main functions to determine the device. Basic usage would be similar to the following.
<?php
include_once "path/to/accessibility.php";
$is_device = is_mobi(); // (1)
$content_type = content_type (); // (2)
/*
1: Is the user browsing with a hand-held or a PC - returns TRUE or FALSE
2: Returns a numerical value that is linked to $mime (3)
3: $mime is described later in this article
*/
You would then make your conditional statements according to these results and execute them as you require. So let's look at accessibility.php in a bit more detail. Please note that this is a real extract from the live module, so there may be a few variables that will not be required.
First we will look at the $mime variable. It contains multiple array values that are linked to content type. The order of these if very important primarily because the array_keys() are interlinked. Secondly as there are 2 text/html content types being checked against it is vital that the first and last elements in the $mime['content'] are of this type. The is due to the fact that the last element is removed from the actual detection. This is only in the code for the module's sake.
<?php
global $mime; (4)
$mime["types"] = array (
"none",
"xhtml",
"html",
"mhtml",
"wml",
"chtml"
);
/*
4: Globalize the variable to make it easier to access within functions
*/
$mime["content"] = array (
"text/html",
"application/xhtml+xml",
"text/html",
"application/vnd.wap.xhtml+xml",
"text/vnd.wap.wml",
"text/html"
);
/*
# The following variables may or may not be required depending on your needs
*/
$mime["dtds"] = array (
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n",
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'."\n",
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n",
'<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN"
"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd"> '."\n",
'<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">',
'<!DOCTYPE html PUBLIC "-//W3C//DTD Compact HTML 1.0 Draft//EN">'."\n"
);
# content types that can have an xml declaration
$mime["xml"] = array (1,3,4);
# content types for mobile devices
$mime["mobis"] = array (3,4,5);
# actual XHTML MP device
$mime["mhtml"] = 3;
# old style phone (WAP)
$mime["wml"] = 4;
# imode type browser
$mime["chtml"] = 5;
As you can see, the array_keys() should match up so for example if application/xhtml+xml was detected in the user_agent string, it would return the value 1. If you then wanted to echo out the correct content type for the page you would do as follows:
<?php
// include code as in snippet 1
echo $mime["content"][$content_type]; // equivalent to $mime["content"][1]
# returns application/xhtml+xml
Before we move on to the actual detection, a few more additional variables / functions need to be defined. These essentially add a few more strings to search for, mobile device including specifically imode, the path to the included file browser.inc and the xml declaration. All the variables are again globalized for ease of use with Drupal.
<?php
global $imode;
$imode = array ("doco", "j-pho", "up.b", "ddip", "port");
global $extra;
$extra = array ("MIDP", "PPC", "symbian", "phone", "palm", "ipaq", "Googlebot-Mobile");
global $xml;
$xml = '<?xml version="1.0" encoding="utf-8"?>'."\n";
global $dir;
$dir = dirname(__FILE__) .'/browser.inc';
//~ Extra function to catch additional mobi parameters: returns > 0 if TRUE
function extra(&$extra) {
$count = 0;
if (is_array($extra)) {
foreach($extra as $findme) {
if ( @stripos(@$_SERVER["HTTP_USER_AGENT"], $findme) !== FALSE ) {
$count++;
}
}
} else {
if ( @stripos(@$_SERVER["HTTP_USER_AGENT"], $extra) !== FALSE ) {
$count++;
}
}
return $count;
}
Finally the fun stuff. The actual detection starts here. This part is fairly self-explanatory. The function determines whether the device is a hand-held or not by checking against all the extra variables created earlier.
<?php
//~ Is the device a mobile ?
function is_mobi($override = FALSE) {
global $imode, $extra, $dir;
// extra trimming of the 4 character strings in the file
$browsers = file( $dir );
foreach ($browsers as $dev) {
$browserlist [] = trim($dev);
}
if ($override == TRUE) { // (5)
return $override;
} elseif ( // (6)
@in_array(strtolower(substr(trim(@$_SERVER["HTTP_USER_AGENT"]),0,4)), $browserlist) OR
@in_array(strtolower(substr(trim(@$_SERVER["HTTP_USER_AGENT"]),0,4)), $imode) OR
extra($extra) > 0
) {
return TRUE;
} else {
return FALSE;
}
}
/*
5: Override variable allows you to set the device to a mobile is_mobi(1) or PC is_mobi(0)
6: Is the agent string in the file, or the $imode array or in the $extra array?
*/
Now we have a TRUE or FALSE value as to whether the device is indeed a hand-held or PC type browser. Final step is to detect the correct content type. Of course, depending on how specific you want to be, you could just stop here - you know what basic device is being used. If you want to go one step further the content_type() function follows.
<?php
//~ Establish best suited content type
function content_type () {
// $accessibility is module related and can be ignored, see snippet 1 for $is_device
global $mime, $accessibility, $is_device;
$is_mobi = (isset($accessibility['is_mobi'])) ? $accessibility['is_mobi'] : $is_device;
// create array matching $mime['types'] to $mime['content']
$accept = array();
foreach ($mime['types'] as $lang => $val){
$accept[$val] = $mime['content'][$lang];
}
//~ remove unwanted elements
$unused = array_shift($accept);
$unused = array_pop($accept);
// set the $c (content type variable as an array)
$c = array();
// If there is no $_SERVER["HTTP_ACCEPT"] then server text/html
if ( !isset($_SERVER["HTTP_ACCEPT"]) OR empty($_SERVER["HTTP_ACCEPT"]) ) {
$c['html'] = 1;
} else {
// loop the the accept types and look for it in the UA string
foreach ($accept as $mime_lang => $mime_type) {
$esc_type = '/'. str_replace( array ('/','.','+'), array('\/','\.','\+'), $mime_type) .";q=0(\.[1-9]+)/i";
$c[$mime_lang] = 1;
if ( @stristr(@$_SERVER["HTTP_ACCEPT"], $mime_type) ) {
$c[$mime_lang] = $c[$mime_lang] + 1;
if (preg_match($esc_type, @$_SERVER["HTTP_ACCEPT"], $matches)) {
$c[$mime_lang] = $c[$mime_lang] - (float)$matches[1];
} // end if pregmatch
} // end if stristr
} // end foreach accept
}
## PART ONE END .. CONTINUED IN NEXT SEGMENT ##
What we have done so far is loop through the mime types and assigned it a value counting how many times the piece exists in our haystack. We have also tried to see if the content type has been given a q value - best suited content type. The output for Firefox with application/vnd.wap.xhtml+xml and text/vnd.wap.wml added would be something follows if the code was debugged.
We then sort the array so that the element with the highest value comes first. The next part is to get rid of the unwanted values, also checking if there are any elements with the same value and systematically eliminating the unneeded ones.
<?php
## PART TWO .. CONTINUED FROM PREVIOUS SEGMENT ##
arsort ($c, SORT_NUMERIC);
if ( array_sum($c) == count($c) ){
unset ( $c );
$c['html'] = 1;
} // end array_sum
// If HTML and a.n.other exist, get rid of anything below max value
$max = max($c);
foreach ($c as $type => $val) {
if ($val != $max) { unset ( $c[$type] ); }
} // end foreach
// eliminate unneeded content types as seen fit
if ( array_key_exists('xhtml', $c) ) { unset ( $c ); $c['xhtml'] = 1; }
if ( array_key_exists('html', $c) ) { unset ( $c ); $c['html'] = 1;}
if ( array_key_exists('html', $c) AND $is_mobi === TRUE ) { unset ( $c ); $c['chtml'] = 1;}
if ( array_key_exists('wml', $c) ) { unset ( $c ); $c['wml'] = 1; }
if ( array_key_exists('mhtml', $c) ) { unset ( $c ); $c['mhtml'] = 1; }
if ( stristr(@$_SERVER["HTTP_USER_AGENT"], 'W3C_Validator') ) { unset ( $c ); $c['xhtml'] = 1; }
// return the actual key value (eg [1])
return array_search(key($c), $mime['types']);
}
That is essentially it. Include the file and call the functions as describe in the very first snippet of code and work with the returned values as required. This script is currently used on the this site and 227net as part of the accessibility module. It is also currently used stand alone on the 227network site entrance and my cv.
The whole code is available for download. Rename the files to browser.inc and accessibility.php respectively. Store both of these files in the same directory. Please take into account that this script uses stripos which is only available for PHP5 and upwards. If you are using PHP4, then try the stripos function for PHP4.
Added Googlebot-Mobile to $extra variable to include Google's mobile browser user-agent.
Comments
Apache Mobile Filter
I have published the new version of Apache Mobile Filter, now the filter is give to you the information of capabilities as apache environment.
Now you can develope in any language (php,jsp, ruby etc.) and have the information of mobile capability.
Read more info here: http://www.idelfuschini.it/it/apache-mobile-filter-v2x.html