Source for file tera_wurfl_parser.php
Documentation is available at tera_wurfl_parser.php
* Tera_WURFL - PHP MySQL driven WURFL
* Tera-WURFL was written by Steve Kamerman, Tera Technologies and is based on the
* WURFL PHP Tools from http://wurfl.sourceforge.net/. This version uses a MySQL database
* to store the entire WURFL file to provide extreme performance increases.
* @author Steve Kamerman, Tera Technologies (kamermans AT teratechnologies DOT net)
* @version Stable 1.5.0 $Date: 2007/02/27 04:23:25 $
* @license http://www.mozilla.org/MPL/ MPL Vesion 1.1
* $Id: tera_wurfl_parser.php,v 1.1.2.3.2.14 2007/02/27 04:23:25 kamermans Exp $
* $RCSfile: tera_wurfl_parser.php,v $
* Based On: WURFL PHP Tools by Andrea Trasatti ( atrasatti AT users DOT sourceforge DOT net )
@require_once('../tera_wurfl_config.php');
// temp storage for the parsed WURFL
// temp storage for the parsed PATCH
// this function checks WURFL patch integrity/validity
global $wurfl, $wurfl_patch, $patch_params, $checkpatch_result, $wurfl_type;
if($wurfl_type == "main"){
}elseif($wurfl_type == "patch"){
$thiswurfl = &$wurfl_patch;
die("Invalid wurfl_type.");
if ( $name == 'wurfl_patch' ) {
$checkpatch_result['wurfl_patch'] = true;
} else if ( !$checkpatch_result['wurfl_patch'] ) {
$checkpatch_result['wurfl_patch'] = false;
toLog('checkpatch', "no wurfl_patch tag! Patch file ignored.");
if ( $name == 'devices' ) {
$checkpatch_result['devices'] = true;
} else if ( !$checkpatch_result['devices'] ) {
$checkpatch_result['devices'] = false;
toLog('checkpatch', "no devices tag! Patch file ignored.");
if ( $name == 'device' ) {
if ( isset ($thiswurfl['devices'][$attr['id']]) ) {
if ( $thiswurfl['devices'][$attr['id']]['user_agent'] != $attr['user_agent'] ) {
$checkpatch_result['device']['id'][$attr["id"]]['patch'] = false;
$checkpatch_result['device']['id'][$attr["id"]]['reason'] = 'user agent mismatch, orig='. $thiswurfl['devices'][$attr['id']]['user_agent']. ', new='. $attr['user_agent']. ', id='. $attr['id']. ', fall_back='. $attr['fall_back'];
* checking if the fall_back is disabled. I might define a device's fall_back which will be defined later in the patch file.
* fall_backs checking could be done after merging.
if ( $attr['id'] == 'generic' && $attr['user_agent'] == '' && $attr['fall_back'] == 'root' ) {
// generic device, everything's ok.
} else if ( !isset($thiswurfl['devices'][$attr['fall_back']]) ) {
$checkpatch_result['device']['id'][$attr["id"]]['patch'] = false;
$checkpatch_result['device']['id'][$attr["id"]]['reason'] .= 'wrong fall_back, id='.$attr['id'].', fall_back='.$attr['fall_back'];
if ( isset ($checkpatch_result['device']['id'][$attr["id"]]['patch'])
&& !$checkpatch_result['device']['id'][$attr["id"]]['patch'] ) {
toLog('checkpatch', $checkpatch_result['device']['id'][$attr["id"]]['reason'],LOG_ERR);
global $wurfl, $wurfl_patch, $curr_event, $curr_device, $curr_group, $fp_cache, $check_patch_params, $checkpatch_result, $wurfl_type;
if($wurfl_type == "main"){
}elseif($wurfl_type == "patch"){
$thiswurfl = &$wurfl_patch;
die("Invalid wurfl_type.");
if ( $check_patch_params ) {
// if the patch file checks fail I don't merge info retrived
toLog('startElement', "error on $name, ". $attr['id'],LOG_ERROR);
$curr_device = 'dump_anything';
} else if ( $curr_device == 'dump_anything' && $name != 'device' ) {
// this capability is referred to a device that was erroneously defined for some reason, skip it
toLog('startElement', $name. " cannot be merged, the device was skipped because of an error",LOG_WARNING);
//cdata will take care of these, I'm just defining the array
//$curr_event=$thiswurfl[$name];
// for the MySQL version I will ignore these (for now)
// TODO: Add support for non-device WURFL tags
// dirty trick: author is child of authors, contributor is child of contributors
while ($t = each($attr)) {
// example: $thiswurfl["authors"]["author"]["name"]="Andrea Trasatti";
$thiswurfl[$name. "s"][$name][$attr["name"]][$t[0]]= $t[1];
if ( ($attr["user_agent"] == "" || ! $attr["user_agent"]) && $attr["id"]!= "generic" ) {
die("No user agent and I am not generic!! id=". $attr["id"]. " HELP");
while ($t = each($attr)) {
// example: $thiswurfl["devices"]["ericsson_generic"]["fall_back"]="generic";
$thiswurfl["devices"][$attr["id"]][$t[0]]= $t[1];
$curr_device= $attr["id"];
// this HAS NOT to be executed or we will define the id as string and then reuse it as array: ERROR
//$thiswurfl["devices"][$curr_device][$attr["id"]]=$attr["id"];
if ( $attr["value"] == 'true' ) {
} else if ( $attr["value"] == 'false' ) {
if ( strcmp($value, $intval) == 0 ) {
$thiswurfl["devices"][$curr_device][$curr_group][$attr["name"]]= $value;
// This might look useless but it's good when you want to parse only the devices and skip the rest
if ( !isset ($thiswurfl["devices"]) )
$thiswurfl["devices"]= array();
// opening tag of the patch file
// opening tag of the WURFL, nothing to do
// unknown events are not welcome
die($name. " is an unknown event<br>");
global $wurfl, $wurfl_patch, $curr_event, $curr_device, $curr_group, $wurfl_type;
if($wurfl_type == "main"){
}elseif($wurfl_type == "patch"){
$thiswurfl = &$wurfl_patch;
die("Invalid wurfl_type.");
$thiswurfl[$name]= $curr_event;
// referring to $GLOBALS to unset curr_event because unset will not destroy
// a global variable unless called in this way
unset ($GLOBALS['curr_event']);
if (trim($data) != "" ) {
//echo "data=".$data."<br>\n";
$droptable = "DROP TABLE IF EXISTS ". $tablename;
$createtable = "CREATE TABLE `". $tablename. "` (
`deviceID` varchar(128) binary NOT NULL default '',
`user_agent` varchar(255) default NULL,
`fall_back` varchar(128) default NULL,
`actual_device_root` tinyint(1) default '0',
`capabilities` mediumtext,
PRIMARY KEY (`deviceID`),
KEY `fallback` (`fall_back`),
KEY `useragent` (`user_agent`)
$emptytable = "DELETE FROM ". $tablename;
function load_wurfl($filetype= "main",$source= "local") {
global $wurfl, $wurfl_patch, $curr_event, $curr_device, $curr_group, $fp_cache, $check_patch_params, $checkpatch_result, $wurfl_type;
if($wurfl_type == "main"){
}elseif($wurfl_type == "patch"){
$thiswurfl = &$wurfl_patch;
if(($source == "remote" || $source == "remote_cvs") && $wurfl_type == "main"){
}elseif($source == "remote_cvs"){
echo "Downloading WURFL from $dl_url ...\n<br/>";
toLog('update',"no write permissions for data directory",LOG_ERR);
die("Fatal Error: The data directory is not writable. (". DATADIR. ")<br/><br/><strong>Please make the data directory writable by the user that runs the webserver process, in Linux this command would do the trick if you're not too concered about security: <pre>chmod -R 777 ". DATADIR. "</pre></strong>");
@ini_set('user_agent', "PHP/Tera-WURFL_$version");
echo "done ($size bytes)<br />";
// ignore this error - I know I'm redefining a constant :P
@define("WURFL_FILE",$newfile);
toLog('parse', $wurflfile. " does not exist",LOG_ERR);
die($wurflfile. " does not exist");
if (!($fp = fopen($wurflfile, "r"))) {
toLog('parse', "$wurflfile could not be opened for XML input",LOG_ERR);
die("$wurflfile could not opened XML input");
while ($data = fread($fp, 4096)) {
toLog('parse',$wurflfile. " ". $errmsg);
die($wurflfile. " ". $errmsg);
$devices = $thiswurfl["devices"];
$processedrows = count($devices);
$insert_errors = array();
foreach($devices as $dev_id => $dev_data) {
* This will detect duplicate device_ids in the WURFL
* if they are different cases. This is important for
* databases like MySQL where keys are sorted without regard
* EDIT: I have disabled this by using 'binary' keys in MySQL
* which are case sensitive.
if(in_array(strtolower($dev_id),$used_ids)){
$insert_errors[] = "Duplicate ID omitted: \"$dev_id\"";
// $wurfl_agents[$one['user_agent']] = $one['id'];
// convert device root to tinyint format (0|1) for db
$devroot = (isset ($dev_data['actual_device_root']) && $dev_data['actual_device_root'])? 1: 0;
if(strlen($dev_data['user_agent']) > 255){
$insert_errors[] = "Warning: user agent too long: \"". $dev['user_agent']. '"';
$insertcache[] = sprintf("(%s,%s,%s,%s,%s)",
$query = "INSERT INTO ". $devtable. " (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES ". implode(",",$insertcache);
$maxquerysize = (strlen($query)> $maxquerysize)? strlen($query): $maxquerysize;
$query = sprintf("INSERT INTO ". $devtable. " (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES (%s,%s,%s,%s,%s)",
$maxquerysize = (strlen($query)> $maxquerysize)? strlen($query): $maxquerysize;
// some records are probably left in the insertcache
$query = "INSERT INTO ". $devtable. " (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES ". implode(",",$insertcache);
$maxquerysize = (strlen($query)> $maxquerysize)? strlen($query): $maxquerysize;
if(count($insert_errors) > 0){
// problem with update - changes will not be applied
echo "There were errors while updating the WURFL. No changes have been made to your database.<br /><br />";
foreach($insert_errors as $error){
toLog("load_wurfl","error inserting device: ". $error,LOG_ERR);
echo "Error inserting device: ". $error. "<br />";
// everything seems to be fine - go ahead and overwrite the production device table (or patch table)
// now our cache is assumed to be tainted since the underlying data may have changed
// I guess it makes sense to clear it even if it's disabled - why not?
toLog("load_wurfl","the $wurfl_type database was successfully updated from: $source",LOG_WARNING);
return(array("total" => $processedrows, "inserted" => $insertedrows, "errors" => $insert_errors, "queries" => $queries, "maxquerysize" => $maxquerysize));
@mysql_query("SELECT COUNT(deviceID) AS num FROM ". $replacement) or die("ERROR: table not found (". $replacement. "): ". mysql_error());
mysql_query("RENAME TABLE `". $replacement. "` TO `". $to_be_replaced. "`") or die("ERROR: could not rename table - make sure the database user has ALTER permissions! Error message: ". mysql_error());
`user_agent` varchar(255) binary NOT NULL default '',
`cache_data` mediumtext NOT NULL,
PRIMARY KEY (`user_agent`)
// find total number of patch records
// fill the hybrid table with the stock WURFL first
// insert all the patch devices that DON'T already exist in the WURFL into the hybrid table
// get all the devices that DO exist in the main WURFL so we can merge them in the hybrid table
|