tera_wurfl
[ class tree: tera_wurfl ] [ index: tera_wurfl ] [ all elements ]

Source for file tera_wurfl_parser.php

Documentation is available at tera_wurfl_parser.php

  1. <?php
  2. /*
  3.  * Tera_WURFL - PHP MySQL driven WURFL
  4.  * 
  5.  * Tera-WURFL was written by Steve Kamerman, Tera Technologies and is based on the
  6.  * WURFL PHP Tools from http://wurfl.sourceforge.net/.  This version uses a MySQL database
  7.  * to store the entire WURFL file to provide extreme performance increases.
  8.  * 
  9.  * @package tera_wurfl
  10.  * @author Steve Kamerman, Tera Technologies (kamermans AT teratechnologies DOT net)
  11.  * @version Stable 1.5.0 $Date: 2007/02/27 04:23:25 $
  12.  * @license http://www.mozilla.org/MPL/ MPL Vesion 1.1
  13.  * $Id: tera_wurfl_parser.php,v 1.1.2.3.2.14 2007/02/27 04:23:25 kamermans Exp $
  14.  * $RCSfile: tera_wurfl_parser.php,v $
  15.  * 
  16.  * Based On: WURFL PHP Tools by Andrea Trasatti ( atrasatti AT users DOT sourceforge DOT net )
  17.  *
  18.  */
  19.  
  20. if !defined('WURFL_CONFIG') )
  21.     @require_once('../tera_wurfl_config.php');
  22.  
  23. if !defined('WURFL_CONFIG') )
  24.     die("NO CONFIGURATION");
  25.  
  26. // temp storage for the parsed WURFL
  27. $wurfl array();
  28. // temp storage for the parsed PATCH
  29. $wurfl_patch array();
  30. $patch_params array();
  31.  
  32. // this function checks WURFL patch integrity/validity
  33. function checkpatch($name$attr{
  34.     global $wurfl$wurfl_patch$patch_params$checkpatch_result$wurfl_type;
  35.     if($wurfl_type == "main"){
  36.         $thiswurfl &$wurfl;
  37.     }elseif($wurfl_type == "patch"){
  38.         $thiswurfl &$wurfl_patch;
  39.     }else{
  40.         die("Invalid wurfl_type.");
  41.     }
  42.     if $name == 'wurfl_patch' {
  43.         $checkpatch_result['wurfl_patch'true;
  44.         return true;
  45.     else if !$checkpatch_result['wurfl_patch'{
  46.         $checkpatch_result['wurfl_patch'false;
  47.         toLog('checkpatch'"no wurfl_patch tag! Patch file ignored.");
  48.         return false;
  49.     }
  50.     if $name == 'devices' {
  51.         $checkpatch_result['devices'true;
  52.         return true;
  53.     else if !$checkpatch_result['devices'{
  54.         $checkpatch_result['devices'false;
  55.         toLog('checkpatch'"no devices tag! Patch file ignored.");
  56.         return false;
  57.     }
  58.     if $name == 'device' {
  59.         if isset($thiswurfl['devices'][$attr['id']]) ) {
  60.             if $thiswurfl['devices'][$attr['id']]['user_agent'!= $attr['user_agent'{
  61.                 $checkpatch_result['device']['id'][$attr["id"]]['patch'false;
  62.                 $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'];
  63.             }
  64.         }
  65.         /*
  66.          * checking if the fall_back is disabled. I might define a device's fall_back which will be defined later in the patch file.
  67.          * fall_backs checking could be done after merging.
  68.         if ( $attr['id'] == 'generic' && $attr['user_agent'] == '' && $attr['fall_back'] == 'root' ) {
  69.             // generic device, everything's ok.
  70.         } else if ( !isset($thiswurfl['devices'][$attr['fall_back']]) ) {
  71.             $checkpatch_result['device']['id'][$attr["id"]]['patch'] = false;
  72.             $checkpatch_result['device']['id'][$attr["id"]]['reason'] .= 'wrong fall_back, id='.$attr['id'].', fall_back='.$attr['fall_back'];
  73.         }
  74.          */
  75.         if isset($checkpatch_result['device']['id'][$attr["id"]]['patch'])
  76.             && !$checkpatch_result['device']['id'][$attr["id"]]['patch'{
  77.             toLog('checkpatch'$checkpatch_result['device']['id'][$attr["id"]]['reason'],LOG_ERR);
  78.             return false;
  79.         }
  80.     }
  81.     return true;
  82. }
  83.  
  84. function startElement($parser$name$attr{
  85.     global $wurfl$wurfl_patch$curr_event$curr_device$curr_group$fp_cache$check_patch_params$checkpatch_result$wurfl_type;
  86.     if($wurfl_type == "main"){
  87.         $thiswurfl &$wurfl;
  88.     }elseif($wurfl_type == "patch"){
  89.         $thiswurfl &$wurfl_patch;
  90.     }else{
  91.         die("Invalid wurfl_type.");
  92.     }
  93.     if $check_patch_params {
  94.         // if the patch file checks fail I don't merge info retrived
  95.         if !checkpatch($name$attr) ) {
  96.             toLog('startElement'"error on $name".$attr['id'],LOG_ERROR);
  97.             $curr_device 'dump_anything';
  98.             return;
  99.         else if $curr_device == 'dump_anything' && $name != 'device' {
  100.             // this capability is referred to a device that was erroneously defined for some reason, skip it
  101.             toLog('startElement'$name." cannot be merged, the device was skipped because of an error",LOG_WARNING);
  102.             return;
  103.         }
  104.     }
  105.  
  106.     switch($name{
  107.         case "ver":
  108.         case "last_updated":
  109.         case "official_url":
  110.         case "statement":
  111.             //cdata will take care of these, I'm just defining the array
  112.             $thiswurfl[$name]="";
  113.             //$curr_event=$thiswurfl[$name];
  114.             break;
  115.         case "maintainers":
  116.         case "maintainer":
  117.         case "authors":
  118.         case "author":
  119.         case "contributors":
  120.         case "contributor":
  121.             // for the MySQL version I will ignore these (for now)
  122.             // TODO: Add support for non-device WURFL tags
  123.             if sizeof($attr{
  124.                 // dirty trick: author is child of authors, contributor is child of contributors
  125.                 while ($t each($attr)) {
  126.                     // example: $thiswurfl["authors"]["author"]["name"]="Andrea Trasatti";
  127.                     $thiswurfl[$name."s"][$name][$attr["name"]][$t[0]]=$t[1];
  128.                 }
  129.             }
  130.             break;
  131.         case "device":
  132.             if ( ($attr["user_agent"== "" || $attr["user_agent"]&& $attr["id"]!="generic" {
  133.                 die("No user agent and I am not generic!! id=".$attr["id"]." HELP");
  134.             }
  135.             if sizeof($attr{
  136.                 while ($t each($attr)) {
  137.                     // example: $thiswurfl["devices"]["ericsson_generic"]["fall_back"]="generic";
  138.                     $thiswurfl["devices"][$attr["id"]][$t[0]]=$t[1];
  139.                 }
  140.             }
  141.             $curr_device=$attr["id"];
  142.             break;
  143.         case "group":
  144.             // this HAS NOT to be executed or we will define the id as string and then reuse it as array: ERROR
  145.             //$thiswurfl["devices"][$curr_device][$attr["id"]]=$attr["id"];
  146.             $curr_group=$attr["id"];
  147.             break;
  148.         case "capability":
  149.             if $attr["value"== 'true' {
  150.                 $value true;
  151.             else if $attr["value"== 'false' {
  152.                 $value =  false;
  153.             else {
  154.                 $value $attr["value"];
  155.                 $intval intval($value);
  156.                 if strcmp($value$intval== {
  157.                     $value $intval;
  158.                 }
  159.             }
  160.             $thiswurfl["devices"][$curr_device][$curr_group][$attr["name"]]=$value;
  161.             break;
  162.         case "devices":
  163.             // This might look useless but it's good when you want to parse only the devices and skip the rest
  164.             if !isset($thiswurfl["devices"]) )
  165.                 $thiswurfl["devices"]=array();
  166.             break;
  167.         case "wurfl_patch":
  168.             // opening tag of the patch file
  169.         case "wurfl":
  170.             // opening tag of the WURFL, nothing to do
  171.             break;
  172.         case "default":
  173.             // unknown events are not welcome
  174.             die($name." is an unknown event<br>");
  175.             break;
  176.     }
  177. }
  178.  
  179. function endElement($parser$name{
  180.     global $wurfl$wurfl_patch$curr_event$curr_device$curr_group$wurfl_type;
  181.     if($wurfl_type == "main"){
  182.         $thiswurfl &$wurfl;
  183.     }elseif($wurfl_type == "patch"){
  184.         $thiswurfl &$wurfl_patch;
  185.     }else{
  186.         die("Invalid wurfl_type.");
  187.     }
  188.     switch ($name{
  189.         case "group":
  190.             break;
  191.         case "device":
  192.             break;
  193.         case "ver":
  194.         case "last_updated":
  195.         case "official_url":
  196.         case "statement":
  197.             $thiswurfl[$name]=$curr_event;
  198.             // referring to $GLOBALS to unset curr_event because unset will not destroy 
  199.             // a global variable unless called in this way
  200.             unset($GLOBALS['curr_event']);
  201.             break;
  202.         default:
  203.             break;
  204.     }
  205.  
  206. }
  207.  
  208. function characterData($parser$data{
  209.     global $curr_event;
  210.     if (trim($data!= "" {
  211.         $curr_event.=$data;
  212.         //echo "data=".$data."<br>\n";
  213.     }
  214. }
  215.  
  216. function emptyWurflDevTable($tablename){
  217.     $droptable "DROP TABLE IF EXISTS ".$tablename;
  218.     $createtable "CREATE TABLE `".$tablename."` (
  219.   `deviceID` varchar(128) binary NOT NULL default '',
  220.   `user_agent` varchar(255) default NULL,
  221.   `fall_back` varchar(128) default NULL,
  222.   `actual_device_root` tinyint(1) default '0',
  223.   `capabilities` mediumtext,
  224.   PRIMARY KEY  (`deviceID`),
  225.   KEY `fallback` (`fall_back`),
  226.   KEY `useragent` (`user_agent`)
  227. ) TYPE=".DB_TYPE;
  228.     $emptytable "DELETE FROM ".$tablename;
  229.     if(DB_EMPTY_METHOD == "DROP_CREATE"){
  230.         mysql_query($droptableor die(mysql_error());
  231.         mysql_query($createtableor die(mysql_error());
  232.     }else{
  233.         mysql_query($emptytableor die(mysql_error());
  234.     }
  235.     return(true);
  236. }
  237.  
  238. function load_wurfl($filetype="main",$source="local"{
  239.     global $wurfl$wurfl_patch$curr_event$curr_device$curr_group$fp_cache$check_patch_params$checkpatch_result$wurfl_type;
  240.     $wurfl_type $filetype;
  241.     if($wurfl_type == "main"){
  242.         $devtable DB_DEVICE_TABLE.DB_TEMP_EXT;
  243.         $prodtable DB_DEVICE_TABLE;
  244.         $wurflfile WURFL_FILE;
  245.         $thiswurfl &$wurfl;
  246.     }elseif($wurfl_type == "patch"){
  247.         $devtable DB_PATCH_TABLE.DB_TEMP_EXT;
  248.         $prodtable DB_PATCH_TABLE;
  249.         $wurflfile WURFL_PATCH_FILE;
  250.         $thiswurfl &$wurfl_patch;
  251.     }
  252.     if(($source == "remote" || $source == "remote_cvs"&& $wurfl_type == "main"){
  253.         if($source == "remote"){
  254.             $dl_url WURFL_DL_URL
  255.         }elseif($source == "remote_cvs"){
  256.             $dl_url WURFL_CVS_URL;
  257.         }
  258.         $newfile DATADIR."dl_wurfl.xml";
  259.         echo "Downloading WURFL from $dl_url ...\n<br/>";
  260.         flush();
  261.         if(!is_writable(DATADIR)){
  262.             toLog('update',"no write permissions for data directory",LOG_ERR);    
  263.             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>");
  264.         }
  265.         @ini_set('user_agent'"PHP/Tera-WURFL_$version");
  266.         die(ini_get('user-agent'));
  267.         $dl_wurfl file_get_contents($dl_url);
  268.         file_put_contents($newfile,$dl_wurfl);
  269.         $size filesize($newfile);
  270.         echo "done ($size bytes)<br />";
  271.         flush();
  272.         // ignore this error - I know I'm redefining a constant :P
  273.         @define("WURFL_FILE",$newfile);
  274.         $wurflfile $newfile;
  275.     }
  276.     $thiswurfl array();
  277.     $xml_parser xml_parser_create();
  278.     xml_parser_set_option($xml_parserXML_OPTION_CASE_FOLDINGfalse);
  279.     xml_set_element_handler($xml_parser"startElement""endElement");
  280.     xml_set_character_data_handler($xml_parser"characterData");
  281.     if !file_exists($wurflfile) ) {
  282.         toLog('parse'$wurflfile." does not exist",LOG_ERR);
  283.         die($wurflfile." does not exist");
  284.     }
  285.     if (!($fp fopen($wurflfile"r"))) {
  286.         toLog('parse'"$wurflfile could not be opened for XML input",LOG_ERR);
  287.         die("$wurflfile could not opened XML input");
  288.     }
  289.     while ($data fread($fp4096)) {
  290.         if (!xml_parse($xml_parser$datafeof($fp))) {
  291.             $errmsg sprintf("XML error: %s at line %d",xml_error_string(xml_get_error_code($xml_parser)),xml_get_current_line_number($xml_parser));
  292.             toLog('parse',$wurflfile." ".$errmsg);
  293.             die($wurflfile." ".$errmsg);
  294.         }
  295.     }
  296.     fclose($fp);
  297.     xml_parser_free($xml_parser);
  298.     $devices $thiswurfl["devices"];
  299.     emptyWurflDevTable($devtable);
  300.     $processedrows count($devices);
  301.     $queries 0;
  302.     $insertedrows 0;
  303.     $maxquerysize 0;
  304.     $insert_errors array();
  305.     $insertcache array();
  306.     $used_ids array();
  307.     foreach($devices as $dev_id => $dev_data{
  308.         /*
  309.          * This will detect duplicate device_ids in the WURFL
  310.          * if they are different cases.  This is important for
  311.          * databases like MySQL where keys are sorted without regard
  312.          * for case.
  313.          * 
  314.          * EDIT: I have disabled this by using 'binary' keys in MySQL
  315.          * which are case sensitive.
  316.          * 
  317.         if(in_array(strtolower($dev_id),$used_ids)){
  318.             $insert_errors[] = "Duplicate ID omitted: \"$dev_id\"";
  319.             continue;
  320.         }else{
  321.             $used_ids[] = $dev_id;
  322.         }
  323.         */
  324.     //    $wurfl_agents[$one['user_agent']] = $one['id'];
  325.         // convert device root to tinyint format (0|1) for db
  326.         $devroot (isset($dev_data['actual_device_root']&& $dev_data['actual_device_root'])10;
  327.         if(strlen($dev_data['user_agent']255){
  328.             $insert_errors["Warning: user agent too long: \"".$dev['user_agent'].'"';
  329.         }
  330.         if(DB_MULTI_INSERT){
  331.             $insertcache[sprintf("(%s,%s,%s,%s,%s)",
  332.             sqlPrep($dev_id),
  333.             sqlPrep($dev_data['user_agent']),
  334.             sqlPrep($dev_data['fall_back']),
  335.             sqlPrep($devroot),
  336.             sqlPrep(serialize($dev_data))
  337.             );
  338.             if(count($insertcache>= DB_MAX_INSERTS){
  339.                 $query "INSERT INTO ".$devtable." (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES ".implode(",",$insertcache);
  340.                 mysql_query($queryor $insert_errors["DB server reported error on id \"$dev_id\": ".mysql_error();
  341.                 $insertedrows += mysql_affected_rows();
  342.                 $insertcache array();
  343.                 $queries++;
  344.                 $maxquerysize (strlen($query)>$maxquerysize)strlen($query)$maxquerysize;
  345.             }
  346.         }else{
  347.             $query sprintf("INSERT INTO ".$devtable." (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES (%s,%s,%s,%s,%s)",
  348.             sqlPrep($dev_id),
  349.             sqlPrep($dev_data['user_agent']),
  350.             sqlPrep($dev_data['fall_back']),
  351.             sqlPrep($devroot),
  352.             sqlPrep(serialize($dev_data))
  353.             );
  354.             mysql_query($queryor $insert_errors[]=mysql_error();
  355.             $insertedrows += mysql_affected_rows();
  356.             $queries++;
  357.             $maxquerysize (strlen($query)>$maxquerysize)strlen($query)$maxquerysize;
  358.         }
  359.     }
  360.     // some records are probably left in the insertcache
  361.     if(DB_MULTI_INSERT && count($insertcache0){
  362.         $query "INSERT INTO ".$devtable." (deviceID, user_agent, fall_back, actual_device_root, capabilities) VALUES ".implode(",",$insertcache);
  363.         mysql_query($queryor $insert_errors[]=mysql_error();
  364.         $insertedrows += mysql_affected_rows();
  365.         $queries++;
  366.         $maxquerysize (strlen($query)>$maxquerysize)strlen($query)$maxquerysize;
  367.     }
  368.     // perform sanity checks
  369.     if(count($insert_errors0){
  370.         // problem with update - changes will not be applied
  371.         echo "There were errors while updating the WURFL.  No changes have been made to your database.<br /><br />";
  372.         foreach($insert_errors as $error){
  373.             toLog("load_wurfl","error inserting device: ".$error,LOG_ERR);
  374.             echo "Error inserting device: ".$error."<br />";
  375.         }
  376.     }else{
  377.         // everything seems to be fine - go ahead and overwrite the production device table (or patch table)
  378.         replace_table($prodtable$devtable);
  379.         // now our cache is assumed to be tainted since the underlying data may have changed
  380.         // I guess it makes sense to clear it even if it's disabled - why not?
  381.         clear_cache();
  382.         toLog("load_wurfl","the $wurfl_type database was successfully updated from$source",LOG_WARNING);
  383.     }
  384.     return(array("total" => $processedrows"inserted" => $insertedrows"errors" => $insert_errors"queries" => $queries"maxquerysize" => $maxquerysize));
  385. }
  386. function replace_table($to_be_replaced$replacement){
  387.     @mysql_query("SELECT COUNT(deviceID) AS num FROM ".$replacementor die("ERROR: table not found (".$replacement."): ".mysql_error());
  388.     mysql_query("DROP TABLE IF EXISTS ".$to_be_replaced);
  389.     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());
  390.     return(true);
  391. }
  392. function clear_cache(){
  393.     mysql_query("DROP TABLE IF EXISTS ".DB_CACHE_TABLE);
  394.     $createtable "CREATE TABLE `".DB_CACHE_TABLE."` (
  395.   `user_agent` varchar(255) binary NOT NULL default '',
  396.   `cache_data` mediumtext NOT NULL,
  397.   PRIMARY KEY  (`user_agent`)
  398. ) TYPE=".DB_TYPE;
  399.     mysql_query($createtableor die("ERROR: could not create cache table (".DB_CACHE_TABLE."): ".mysql_error());
  400.     return(true);
  401. }
  402. function apply_patch(){
  403.     $queries 1;
  404.     $merge_errors array();
  405.     // find total number of patch records
  406.     $res mysql_query("SELECT COUNT(deviceID) AS num FROM ".DB_PATCH_TABLE);
  407.     $processedrows mysql_result($res,0,'num');
  408.     $queries++;
  409.     // fill the hybrid table with the stock WURFL first
  410.     $fillhybrid "INSERT INTO ".DB_HYBRID_TABLE." SELECT * FROM ".DB_DEVICE_TABLE;
  411.     mysql_query($fillhybrid);
  412.     $queries++;
  413.     // insert all the patch devices that DON'T already exist in the WURFL into the hybrid table
  414.     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");
  415.     $queries++;
  416.     $newdevs mysql_affected_rows();
  417.     // get all the devices that DO exist in the main WURFL so we can merge them in the hybrid table
  418.     $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");
  419.     $queries++;