This article provides a quick way to make your Drupal site accessible to mobile devices through two options:
- Have a one site serve multiple devices using one code base
- Use multisites to render content accordingly using one code base
By doing this your site should be simultaneously available to both mobile and desktop browsers.
Table of Contents
- 1. Overview
- Outline of what this article is all about
- 2. Function of this script
- The scripts functionality, what is does and what it does not
- 3. Skill Level
- Developer knowledge required to implement the script
- 4. Requirements
- Server set up
- 5. Files Included
- All the files included in this module
- 6. Usage
- How to use
- 7. Code
- Summary of the relevant code arrange into snippets
- 8. Configuration
- Summary of the relevant code arrange into snippets
- 9. Summary
- Overview of what has been achieved
- 10. References and additional information
- Inspiration and future developments
1. Overview
This article provides a quick way to make your site accessible to mobile devices through two options
- Have a one site serve multiple devices using one code base
- Use multisites to render content accordingly using one code base
The basis of the scripts include could be used stand-alone. The template engine used in this article is phptemplate
.
2. Function of this script
The scripts included in this article checks to see if the device is a handheld browser using $_SERVER["HTTP_USER_AGENT"]
, and works out the best type of data to send to it using $_SERVER["HTTP_ACCEPT"]
values.
3. Skill Level
Intermediate PHP and Drupal knowledge level is required. Developer must have prior knowledge of setting up multisites.
4. Requirements
The script uses PHP. This has been tested in PHP v4x and above, but it should work in any PHP environment. The script is an adaptation used on Drupal 4.5 / 6. It is untested in 4.7 and may require a few tweaks.
5. Files Included
A list of the files included in this module. Please ensure that files are uploaded to the correct location.
- settings.php
- Modifications only to this file 5.1
- themes [directory] 5.2
-
- mhtml – directory containing
page.tpl.php
for mobile devices - imode – directory containing
page.tpl.php
for imode web browsers - wml – directory containing
page.tpl.php
wap compatible template
- mhtml – directory containing
- detect [directory]
-
browser.php
– device detection scriptbrowser.inc
– text file containing list of all known mobile devicesfix_code.php
– includes functions for tidying up final output
5.1 Directories within sites
dependant on site set up
5.2 Screenshots, CSS, icons and additional templates can be added as required
6. Usage
Upload the detect directory to chosen location, then edit paths in settings.php
accordingly. To use multisites, please see configuration for more details.
7. Code
The actual code in more detail.
Changes to settings.php
set some header parameters that must be set before any content is sent to the browser. Drupal has a couple of built in headers that need to be overridden. Changes are therefore required to a couple of lines in common.inc
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// common.inc line 60 function drupal_get_html_head() { global $base_url; $output = "<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />n"; $output .= "<base href="$base_url/" />n"; $output .= theme('stylesheet_import', 'misc/drupal.css'); return $output . drupal_set_html_head(); } // common.inc line 1875 drupal_set_header('Content-Type: text/html; charset=utf-8'); |
Change to the following. This overrides the default Content-Type. Note that if roll-back is required, common.inc
must be reverted also.
1 2 3 4 5 6 7 8 |
// common.inc line 60 function drupal_get_html_head() { global $output; return $output . drupal_set_html_head(); // common.inc line 1875 #drupal_set_header('Content-Type: text/html; charset=utf-8'); |
7.1 settings.php
Towards the bottom of settings.php
include the main file browser.php
. Call the function and make some changes accordingly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function html_fix($buffer) { return (preg_replace("!s*/>!", ">", $buffer)); } $accept = array( "xhtml" => "application/xhtml+xml", "html" => "text/html", "wml" => "text/vnd.wap.wml", "mhtml" => "application/vnd.wap.xhtml+xml", ); $mobi = array("chtml", "wml", "mhtml"); DEFINE ('DETECT_PATH', '/path/to/detect_directory/'); // change to suit include_once DETECT_PATH .'browser.php'; $browserfile = DETECT_PATH .'browser.inc'; $fixfile = DETECT_PATH .'fix_code.php'; $or = FALSE; // Options: FALSE or keys from $accept $detect = media_type($accept, $browserfile, $or); if ((defined('MOBI') AND MOBI == true) OR in_array($detect, $mobi)) { $is_mobi = true; } else { $is_mobi = false; } |
browser.php
should return a key from the $accept
array. $or
enables complete override of the detection. This is site wide, so if $or = 'wml';
the whole site will be in wml
. Paths to files should be amended also. The next snippet sets http headers and then detects if the device is a mobile and restricts the number of snippets view on the main node page.
1 2 3 4 5 6 7 |
drupal_set_header('Content-Type: '. $accept[$detect] .'; charset=utf-8'); drupal_set_header('Vary: Accept'); if ($is_mobi == true) { $conf['default_nodes_main'] = 2; $conf['anonymous'] = 'Mobile User'; } |
The final part of the script sets additional headers and then automatically selects a template dependent of device.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
$default_css = 'misc/drupal.css'; // default drupal style if ($detect == 'wml') { drupal_set_header('Content-Type: '. $accept[$detect] .', true'); include $fixfile; // extra file for stripping out non-wml syntax ob_start('fix_code'); $conf['theme_custom'] = 'wap'; // change to suit $conf['theme_default'] = 'wap'; // change to suit $output = ''; } elseif ($detect == 'mhtml') { $conf['theme_custom'] = 'mobi'; // change to suit $conf['theme_default'] = 'mobi'; // change to suit $output = '<meta http-equiv="Content-Type" content="'. $accept[$detect] .'; charset=utf-8" />'."n"; $output .= '<base href="'. $base_url .'/" />'."n"; $output .= theme('stylesheet_import', $default_css); elseif ($detect == 'chtml') { $conf['theme_custom'] = 'imode'; // change to suit $conf['theme_default'] = 'imode'; // change to suit ob_start('html_fix'); $output = '<meta http-equiv="Content-Type" content="'. $accept[$detect] .'; charset=utf-8">'."n"; $output .= '<base href="'. $base_url .'/" />'."n"; $output .= theme('stylesheet_import', $default_css); elseif ($detect == 'xhtml' AND $is_mobi == true) { $conf['theme_custom'] = 'mobi'; // change to suit $conf['theme_default'] = 'mobi'; // change to suit $output = '<meta http-equiv="Content-Type" content="'. $accept[$detect] .'; charset=utf-8" />'."n"; $output .= '<base href="'. $base_url .'/" />'."n"; $output .= theme('stylesheet_import', $default_css); } elseif ($detect == 'html') { ob_start('html_fix'); $output = '<meta http-equiv="Content-Type" content="'. $accept[$detect] .'; charset=utf-8">'."n"; $output .= '<base href="'. $base_url .'/" />'."n"; $output .= theme('stylesheet_import', $default_css); } else { $output = '<meta http-equiv="Content-Type" content="'. $accept[$detect] .'; charset=utf-8" />'."n"; $output .= '<base href="'. $base_url .'/" />'."n"; $output .= theme('stylesheet_import', $default_css); } |
7.2 Themes
Themes are made using phptemplate for Drupal. They work as any other template. However, they render content slightly differently using different DTDs according to device capability. Some examples are listed below.
- wml 7.1
-
123456789101112131415<?xml version="1.0" encoding="utf-8"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"><wml><card cardid="content" cardtitle="<?php print $site_name; ?>"><?php if ($title) ?><p><big><?php print $title; ?></big></p><?php ?><?php if ($site_slogan) ?><p><em><?php print $site_slogan; ?></em></p><?php ?><?php print $content; ?><p><a href="http://mobiforge.com/#menu">Menu</a></p></card><card cardid="menu" cardtitle="Menu"><?php if (isset($primary_links)) { ?><p><?php print theme('links', $primary_links) ?></p><?php } ?></card></wml>
- mhtml
-
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN""http://www.wapforum.org/DTD/xhtml-mobile10.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language ?>" xml:lang="<?php print $language ?>"><head><title><?php print $head_title ?></title><link rel="Home" href="/" title="Network Home" /><link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /><meta http-equiv="Content-Style-Type" content="text/css" /><?php print $head ?><?php print $styles ?></head><body><div id="home"><?php if ($logo) : ?><a accesskey="1" href="<?php print $base_path ?>"title="<?php print t('Home') ?>"><img src="<?php print($logo) ?>"alt="<?php print t('Home') ?>" border="0" /></a><?php endif; ?></div><?php if ($site_name) : ?><div class="site-name"><a href="<?php print $base_path ?>"title="<?php print t('Home') ?>"><?php print($site_name) ?></a></div><?php endif;?><?php if ($site_slogan) : ?><div class="site-slogan"><?php print($site_slogan) ?></div><?php endif;?><?php if (isset($primary_links)) { ?><p><?php print theme('links', $primary_links) ?></p><?php } ?><?php if (isset($secondary_links)) { ?><p><?php print theme('links', $secondary_links) ?></p><?php } ?><?php print $content; ?><?php if ($sidebar_left != ""): ?><div id="sidebar-left"><?php print $sidebar_left ?></div><?php endif; ?><?php if ($footer_message) : ?><div id="footer-message"><p><?php print $footer_message;?></p></div><?php endif; ?></body></html>
These are stripped down versions of phptemplate
. Some variables, that are unnecessary for a mobile device, have been removed. imode
's template is similar, except it is written in Compact HTML. xhtml
and html
are fully-blown templates, written in xhtml and html respectively, and are targeted for non-handheld devices.
7.1 Note that cardid and cardtitle are deliberately incorrect here. fix_code.php
corrects these later.
7.3 Detect
The main file is browser.php
. This returns best suited $_SERVER["HTTP_ACCEPT"]
and whether it is a hand-held device or not checking against $_SERVER["HTTP_USER_AGENT"]
. This can be adapted to be a stand-alone script, but it has dependencies.
- $accept
- An array equivalent to the following, containing content-types.
123456$accept = array("xhtml" => "application/xhtml+xml","html" => "text/html","wml" => "text/vnd.wap.wml","mhtml" => "application/vnd.wap.xhtml+xml");The array key is straight forward, the array value is the full accept_type. This is used subsequently used in
drupal_set_html_head()
anddrupal_set_header()
respectively. - $browserlist
- This creates an array using
1file()
to open
browser.inc
. Contained inbrowser.inc
is a list of the first four characters of the user_agent string of known devices. Each device is listed on a new line. Extract below.
alav
alca
armv
au-m
Having created the 2 arrays listed above, the script now starts to check the $_SERVER["HTTP_ACCEPT"]
, including q value
, so if the device accepts more than one mime type, the script should pick the best one. If there is no q value
but $accept
is there then 1 is added.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
function media_type(&$accept, $browserfile, $mime = false) { $is_mobi = false; // note: this variable only exists within this function if ( $mime !== false ) { return $mime; } elseif (stristr($_SERVER["HTTP_USER_AGENT"],"W3C_Validator")) { return $mime = 'xhtml'; } elseif (!isset($_SERVER["HTTP_ACCEPT"])) { return $mime = 'html'; } else { $mime = 'html'; $c = array(); 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]; } $iMode = array('doco', 'j-pho', 'up.b', 'ddip', 'port'); if (in_array(strtolower(substr(trim($_SERVER['HTTP_USER_AGENT']),0,4)), $iMode)) { $mime = 'chtml'; $is_mobi = true; } else { arsort ($c, SORT_NUMERIC); /* The aim is to reduce $c to one element, the best suited accept_type */ if ( array_sum($c) == count($c) ) { unset ( $c ); $c['html'] = 1; } $max = max($c); foreach ($c as $type => $val) { if ($val != $max) unset ( $c[$type] ); } $browsers = file($browserfile); foreach ($browsers as $dev) $browserlist [] = trim($dev); if (in_array(strtolower(substr(trim($_SERVER['HTTP_USER_AGENT']),0,4)), $browserlist)) $is_mobi = 1; 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('wml', $c) ) {unset ( $c ); $c['wml'] = 1;} if ( array_key_exists('mhtml', $c)) {unset ( $c ); $c['mhtml'] = 1;} $mime = key($c); } define ('MOBI', $is_mobi); return $mime; } } |
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.
Array
(
[xhtml] => 2
[html] => 1.1
[wml] => 1.4
[mhtml] => 2
)
For Firefox the final result should be xhtml
and MOBI = false
.
8. Configuration
fix_code.php
is a file containing a similar function to html_fix()
. It strips out all non-wml syntax using preg_replace
and str_replace
and is called when ob_start is called. Information about this file has been included here as it should be edited further to strip out any additional content that is not required.
Brief details on how to use these files were outlined here, additional information follows.
One site serving multiple devices using one code base
- Upload all files
- Log in as an administrator and enable all the uploaded templates
- Make the required changes to
common.inc
- Amend the
settings.php
file insites/default
directory as detailed previously
Multisites using one code base
For more information on how to set up multisites with Drupal, please visit: instructions or overview.
- Upload all files
- Log in as an administrator and enable all the uploaded templates
- Make the required changes to
common.inc
- Amend the
settings.php
file insites/device_name
directory specifying the$or
to match the device of choice 8.1
8.1 The whole detection script does not need to be included in settings.php
if the device type is know. Below is an example for WAP / WML site.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
DEFINE ('DETECT_PATH', '/path/to/detect_directory/'); // change to suit $fixfile = DETECT_PATH .'fix_code.php'; drupal_set_header('Content-Type: text/vnd.wap.wml; charset=utf-8'); drupal_set_header('Vary: Accept'); $conf['default_nodes_main'] = 2; $conf['anonymous'] = 'Mobile User'; drupal_set_header('Content-Type: text/vnd.wap.wml, true'); include $fixfile; // extra file for stripping out non-wml syntax ob_start('fix_code'); $conf['theme_custom'] = 'wap'; // change to suit $conf['theme_default'] = 'wap'; // change to suit $output = ''; |
9. Summary
This article utilizes a simple device detection script and selects the relevant output. Further changes may be needed to strip out irrelevant content for mobile devices, most of which can be achieved by editing fix_code.php
.
10. References and additional information
References and additional information.
Status
This document is draft. Development work has progressed to create a module to include this functionality. Files can be downloaded in zip format here.
References and Credits
- Serving up XHTML with the correct MIME type
- Sending XHTML as text/html Considered Harmful
- XHTML Media Types
- Web Developer Guide
- W3C Mobile Web Best Practices
- XHTML Mobile Profile 1.0
Comments
Send comments or queries here
Additional
Working example of this script can be seen at http://www.227net.com. Please note that due to limitation in sending XHTML with the correct content-type XHTML compatible browser still receive content as text/html
. Multisites have also been enabled. Device dependant sites are listed below: