Location: PHPKode > scripts > Tera WURFL > tera-wurfl/admin/tera_wurfl_parser.php
<?php
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WURFL PHP Libraries.
 *
 * The Initial Developer of the Original Code is
 * Andrea Trasatti.
 * Portions created by the Initial Developer are Copyright (C) 2005
 * the Initial Developer. All Rights Reserved.
 *
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * $Id: tera_wurfl_parser.php,v 1.1.2.3.2.2 2006/10/27 01:34:35 kamermans Exp $
 * $RCSfile: tera_wurfl_parser.php,v $ v2.1 beta2 (Apr, 16 2005)
 * 
 * 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)
 * Based On: WURFL PHP Tools by Andrea Trasatti ( atrasatti AT users DOT sourceforge DOT net )
 *
 */

if ( !defined('WURFL_CONFIG') )
	@require_once('../tera_wurfl_config.php');

if ( !defined('WURFL_CONFIG') )
	die("NO CONFIGURATION");

// temp storage for the parsed WURFL
$wurfl = array();
// temp storage for the parsed PATCH
$wurfl_patch = array();
$patch_params = array();

// this function check WURFL patch integrity/validity
function checkpatch($name, $attr) {
	global $wurfl, $wurfl_patch, $patch_params, $checkpatch_result, $wurfl_type;
	if($wurfl_type == "main"){
		$thiswurfl = &$wurfl;
	}elseif($wurfl_type == "patch"){
		$thiswurfl = &$wurfl_patch;
	}else{
		die("Invalid wurfl_type.");
	}
	if ( $name == 'wurfl_patch' ) {
		$checkpatch_result['wurfl_patch'] = true;
		return true;
	} else if ( !$checkpatch_result['wurfl_patch'] ) {
		$checkpatch_result['wurfl_patch'] = false;
		wurfl_log('checkpatch', "no wurfl_patch tag! Patch file ignored.");
		return false;
	}
	if ( $name == 'devices' ) {
		$checkpatch_result['devices'] = true;
		return true;
	} else if ( !$checkpatch_result['devices'] ) {
		$checkpatch_result['devices'] = false;
		wurfl_log('checkpatch', "no devices tag! Patch file ignored.");
		return false;
	}
	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 of 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'] ) {
			wurfl_log('checkpatch', "ERROR:".$checkpatch_result['device']['id'][$attr["id"]]['reason']);
			return false;
		}
	}
	return true;
}

function startElement($parser, $name, $attr) {
	global $wurfl, $wurfl_patch, $curr_event, $curr_device, $curr_group, $fp_cache, $check_patch_params, $checkpatch_result, $wurfl_type;
	if($wurfl_type == "main"){
		$thiswurfl = &$wurfl;
	}elseif($wurfl_type == "patch"){
		$thiswurfl = &$wurfl_patch;
	}else{
		die("Invalid wurfl_type.");
	}
	if ( $check_patch_params ) {
		// if the patch file checks fail I don't merge info retrived
		if ( !checkpatch($name, $attr) ) {
			wurfl_log('startElement', "error on $name, ".$attr['id']);
			$curr_device = 'dump_anything';
			return;
		} else if ( $curr_device == 'dump_anything' && $name != 'device' ) {
			// this capability is referred to a device that was erroneously defined for some reason, skip it
			wurfl_log('startElement', $name." cannot be merged, the device was skipped because of an error");
			return;
		}
	}

	switch($name) {
		case "ver":
		case "last_updated":
		case "official_url":
		case "statement":
			//cdata will take care of these, I'm just defining the array
			$thiswurfl[$name]="";
			//$curr_event=$thiswurfl[$name];
			break;
		case "maintainers":
		case "maintainer":
		case "authors":
		case "author":
		case "contributors":
		case "contributor":
			// for the MySQL version I will ignore these (for now)
			// TODO: Add support for non-device WURFL tags
			if ( sizeof($attr) > 0 ) {
				// 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];
				}
			}
			break;
		case "device":
			if ( ($attr["user_agent"] == "" || ! $attr["user_agent"]) && $attr["id"]!="generic" ) {
				die("No user agent and I am not generic!! id=".$attr["id"]." HELP");
			}
			if ( sizeof($attr) > 0 ) {
				while ($t = each($attr)) {
					// example: $thiswurfl["devices"]["ericsson_generic"]["fall_back"]="generic";
					$thiswurfl["devices"][$attr["id"]][$t[0]]=$t[1];
				}
			}
			$curr_device=$attr["id"];
			break;
		case "group":
			// 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"];
			$curr_group=$attr["id"];
			break;
		case "capability":
			if ( $attr["value"] == 'true' ) {
				$value = true;
			} else if ( $attr["value"] == 'false' ) {
				$value =  false;
			} else {
				$value = $attr["value"];
				$intval = intval($value);
				if ( strcmp($value, $intval) == 0 ) {
					$value = $intval;
				}
			}
			$thiswurfl["devices"][$curr_device][$curr_group][$attr["name"]]=$value;
			break;
		case "devices":
			// 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();
			break;
		case "wurfl_patch":
			// opening tag of the patch file
		case "wurfl":
			// opening tag of the WURFL, nothing to do
			break;
		case "default":
			// unknown events are not welcome
			die($name." is an unknown event<br>");
			break;
	}
}

function endElement($parser, $name) {
	global $wurfl, $wurfl_patch, $curr_event, $curr_device, $curr_group, $wurfl_type;
	if($wurfl_type == "main"){
		$thiswurfl = &$wurfl;
	}elseif($wurfl_type == "patch"){
		$thiswurfl = &$wurfl_patch;
	}else{
		die("Invalid wurfl_type.");
	}
	switch ($name) {
		case "group":
			break;
		case "device":
			break;
		case "ver":
		case "last_updated":
		case "official_url":
		case "statement":
			$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']);
			break;
		default:
			break;
	}

}

function characterData($parser, $data) {
	global $curr_event;
	if (trim($data) != "" ) {
		$curr_event.=$data;
		//echo "data=".$data."<br>\n";
	}
}

function emptyWurflDevTable($tablename){
	$droptable = "DROP TABLE IF EXISTS ".$tablename;
	$createtable = "CREATE TABLE `".$tablename."` (
  `deviceID` varchar(128) 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`)
) TYPE=".DB_TYPE;
	$emptytable = "DELETE FROM ".$tablename;
	if(DB_EMPTY_METHOD == "DROP_CREATE"){
		mysql_query($droptable) or die(mysql_error());
		mysql_query($createtable) or die(mysql_error());
	}else{
		mysql_query($emptytable) or die(mysql_error());
	}
	return(true);
}

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;
	$wurfl_type = $filetype;
	if($wurfl_type == "main"){
		$devtable = DB_DEVICE_TABLE.DB_TEMP_EXT;
		$prodtable = DB_DEVICE_TABLE;
		$wurflfile = WURFL_FILE;
		$thiswurfl = &$wurfl;
	}elseif($wurfl_type == "patch"){
		$devtable = DB_PATCH_TABLE.DB_TEMP_EXT;
		$prodtable = DB_PATCH_TABLE;
		$wurflfile = WURFL_PATCH_FILE;
		$thiswurfl = &$wurfl_patch;
	}
	if($source == "remote" && $type="main"){
		$newfile = DATADIR."dl_wurfl.xml";
		echo "Downloading WURFL...";
		flush();
		$dl_wurfl = file_get_contents(WURFL_DL_URL);
		file_put_contents($newfile,$dl_wurfl);
		$size = filesize($newfile);
		echo "done ($size bytes)<br />";
		flush();
		define("WURFL_FILE",$newfile);
		$wurflfile = $newfile;
	}
	$thiswurfl = array();
	$xml_parser = xml_parser_create();
	xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false);
	xml_set_element_handler($xml_parser, "startElement", "endElement");
	xml_set_character_data_handler($xml_parser, "characterData");
	if ( !file_exists($wurflfile) ) {
		wurfl_log('parse', $wurflfile." does not exist");
		die($wurflfile." does not exist");
	}
	if (!($fp = fopen($wurflfile, "r"))) {
		wurfl_log('parse', "could not open XML input");
		die("could not open XML input");
	}
	while ($data = fread($fp, 4096)) {
		if (!xml_parse($xml_parser, $data, feof($fp))) {
			die(sprintf("XML error: %s at line %d",
			    xml_error_string(xml_get_error_code($xml_parser)),
			    xml_get_current_line_number($xml_parser)));
		}
	}
	fclose($fp);
	xml_parser_free($xml_parser);
	$devices = $thiswurfl["devices"];
	emptyWurflDevTable($devtable);
	$processedrows = count($devices);
	$queries = 0;
	$insertedrows = 0;
	$maxquerysize = 0;
	$insert_errors = array();
	$insertcache = array();
	foreach($devices as $dev_id => $dev_data) {
	//	$wurfl_agents[$one['user_agent']] = $one['id'];
		// convert device root to tinyint format (0|1) for db
		$devroot = ($dev_data['actual_device_root'])? 1: 0;
		if(strlen($dev_data['user_agent']) > 255){
			$insert_errors[] = "Warning: user agent too long: \"".$dev['user_agent'].'"';
		}
		if(DB_MULTI_INSERTS){
			$insertcache[] = sprintf("(%s,%s,%s,%s,%s)",
			sqlPrep($dev_id),
			sqlPrep($dev_data['user_agent']),
			sqlPrep($dev_data['fall_back']),
			sqlPrep($devroot),
			sqlPrep(serialize($dev_data))
			);
			if(count($insertcache) >= DB_MAX_INSERTS){
				$query = "INSERT INTO ".$devtable." (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES ".implode(",",$insertcache);
				mysql_query($query) or $insert_errors[]=mysql_error();
				$insertedrows += mysql_affected_rows();
				$insertcache = array();
				$queries++;
				$maxquerysize = (strlen($query)>$maxquerysize)? strlen($query): $maxquerysize;
			}
		}else{
			$query = sprintf("INSERT INTO ".$devtable." (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES (%s,%s,%s,%s,%s)",
			sqlPrep($dev_id),
			sqlPrep($dev_data['user_agent']),
			sqlPrep($dev_data['fall_back']),
			sqlPrep($devroot),
			sqlPrep(serialize($dev_data))
			);
			mysql_query($query) or $insert_errors[]=mysql_error();
			$insertedrows += mysql_affected_rows();
			$queries++;
			$maxquerysize = (strlen($query)>$maxquerysize)? strlen($query): $maxquerysize;
		}
	}
	// some records are probably left in the insertcache
	if(DB_MULTI_INSERT && count($insertcache) > 0){
		$query = "INSERT INTO ".$devtable." (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES ".implode(",",$insertcache);
		mysql_query($query) or $insert_errors[]=mysql_error();
		$insertedrows += mysql_affected_rows();
		$queries++;
		$maxquerysize = (strlen($query)>$maxquerysize)? strlen($query): $maxquerysize;
	}
	// perform sanity checks
	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){
			echo "Error INSERTing device: ".$error."<br />";
		}
	}else{
		// everything seems to be fine
		replace_table($prodtable, $devtable);
	}
	return(array("total" => $processedrows, "inserted" => $insertedrows, "errors" => $insert_errors, "queries" => $queries, "maxquerysize" => $maxquerysize));
}
function replace_table($to_be_replaced, $replacement){
	@mysql_query("SELECT COUNT(deviceID) AS num FROM ".$replacement) or die("ERROR: Device table not found (".$replacement."): ".mysql_error());
	mysql_query("DROP TABLE IF EXISTS ".$to_be_replaced);
	mysql_query("RENAME TABLE `".$replacement."` TO `".$to_be_replaced."`");
	return(true);
}
function apply_patch(){
	emptyWurflDevTable(DB_HYBRID_TABLE);
	$queries = 1;
	$merge_errors = array();
	// find total number of patch records
	$res = mysql_query("SELECT COUNT(deviceID) AS num FROM ".DB_PATCH_TABLE);
	$processedrows = mysql_result($res,0,'num');
	$queries++;
	// fill the hybrid table with the stock WURFL first
	$fillhybrid = "INSERT INTO ".DB_HYBRID_TABLE." SELECT * FROM ".DB_DEVICE_TABLE;
	mysql_query($fillhybrid);
	$queries++;
	// insert all the patch devices that DON'T already exist in the WURFL into the hybrid table
	mysql_query("INSERT INTO ".DB_HYBRID_TABLE." SELECT p.* FROM ".DB_PATCH_TABLE." AS p LEFT JOIN ".DB_HYBRID_TABLE." AS d ON p.deviceID = d.deviceID WHERE d.deviceID IS NULL");
	$queries++;
	$newdevs = mysql_affected_rows();
	// get all the devices that DO exist in the main WURFL so we can merge them in the hybrid table
	$patchres = mysql_query("SELECT p.* FROM ".DB_PATCH_TABLE." AS p LEFT JOIN ".DB_DEVICE_TABLE." AS d ON p.deviceID = d.deviceID WHERE d.deviceID IS NOT NULL");
	$queries++;
	$mergeddevs = mysql_num_rows($patchres);
	while($new = mysql_fetch_assoc($patchres)){
		// grab the original record to merge with the new one
		$origres = mysql_query("SELECT * FROM ".DB_HYBRID_TABLE." WHERE deviceID=".sqlPrep($new['deviceID']));
		$queries++;
		$orig = mysql_fetch_assoc($origres);
		$origcap = unserialize($orig['capabilities']);
		$newcap = unserialize($new['capabilities']);
		$merged = $new;
		$mergedcap = $origcap;
		foreach($newcap as $key => $val) {
			if ( is_array($val) ) {
				$mergedcap[$key] = array_merge($mergedcap[$key], $val);
			} else {
				$mergedcap[$key] = $val;
			}
		}
		$merged['capabilities'] = serialize($mergedcap);
		// now we should have a merged record - update it
		$setstringarr = array();
		foreach($merged as $key => $val){
			$setstringarr[] = $key."=".sqlPrep($val);
		}
		$setstring = implode(", ",$setstringarr);
		mysql_query("UPDATE ".DB_HYBRID_TABLE." SET ".$setstring." WHERE deviceID=".sqlPrep($merged['deviceID']));
		$queries++;
	}
	return(array("total" => $processedrows, "new" => $newdevs, "merged" => $mergeddevs, "errors" => $merge_errors, "queries" => $queries));
}
function sqlPrep($value){
	if (get_magic_quotes_gpc()) $value = stripslashes($value);
	if($value == '') $value = 'NULL';
	else if (!is_numeric($value) || $value[0] == '0') $value = "'" . mysql_real_escape_string($value) . "'"; //Quote if not integer
	return $value;
}
function wurfl_log($func, $msg, $logtype=3) {
	// Thanks laacz
	$_textToLog = date('r')." [".php_uname('n')." ".getmypid()."]"."[$func] ".$msg;

	if ( $logtype == 3 && is_file(WURFL_LOG_FILE) ) {
		if ( !@error_log($_textToLog."\n", 3, WURFL_LOG_FILE) ) {
			error_log("Unable to log to ".WURFL_LOG_FILE." log_message:$_textToLog"); // logging in the webserver's log file
		}
	} else {
		error_log($_textToLog); // logging in the webserver's log file
	}
}

// PHP4 does not have file_put_contents() so I emulate it if it's not defined
if(!function_exists("file_put_contents")){
	function file_put_contents($n, $d, $flag = false) {
	   $mode = ($flag == FILE_APPEND || strtoupper($flag) == 'FILE_APPEND') ? 'a' : 'w';
	   $f = @fopen($n, $mode);
	   if ($f === false) {
	       return 0;
	   } else {
	       if (is_array($d)) $d = implode($d);
	       $bytes_written = fwrite($f, $d);
	       fclose($f);
	       return $bytes_written;
	   }
	}
}
// You don't really NEED a local WURFL file, you can just use
//if ( !file_exists(WURFL_FILE) ) {
//	wurfl_log('main', WURFL_FILE." does not exist");
//	die(WURFL_FILE." does not exist");
//}
?>
Return current item: Tera WURFL