Source for file TeraWurfl.php
Documentation is available at TeraWurfl.php
* Tera_WURFL - PHP MySQL driven WURFL
* Tera-WURFL was written by Steve Kamerman, and is based on the
* Java WURFL Evolution package by Luca Passani and WURFL PHP Tools by Andrea Trassati.
* This version uses a MySQL database to store the entire WURFL file, multiple patch
* files, and a persistent caching mechanism to provide extreme performance increases.
* @author Steve Kamerman <stevekamerman AT gmail.com>
* @version Stable 2.1.3 $Date: 2010/09/18 15:43:21
* @license http://www.mozilla.org/MPL/ MPL Vesion 1.1
require_once realpath(dirname(__FILE__ ). '/TeraWurflConfig.php');
require_once realpath(dirname(__FILE__ ). '/DatabaseConnectors/TeraWurflDatabase.php');
require_once realpath(dirname(__FILE__ ). '/TeraWurflLoader.php');
require_once realpath(dirname(__FILE__ ). '/UserAgentFactory.php');
require_once realpath(dirname(__FILE__ ). '/UserAgentUtils.php');
require_once realpath(dirname(__FILE__ ). '/WurflConstants.php');
require_once realpath(dirname(__FILE__ ). '/WurflSupport.php');
require_once realpath(dirname(__FILE__ ). '/UserAgentMatchers/UserAgentMatcher.php');
* The main Tera-WURFL Class, provides all end-user methods and properties for interacting
public static $SETTING_WURFL_VERSION = 'wurfl_version';
public static $SETTING_WURFL_DATE = 'wurfl_date';
public static $SETTING_LOADED_DATE = 'loaded_date';
public static $SETTING_PATCHES_LOADED = 'patches_loaded';
* Array of errors that were encountered while processing the request
* Array of WURFL capabilities of the requested device
* Database connector to be used, must extend TeraWurflDatabase. All database functions are performed
* in the database connector through its methods and properties.
* @see TeraWurflDatabase_MySQL5
* The directory that TeraWurfl.php is in
* The user agent that is being evaluated
* The HTTP Accept header that is being evaluated
* The UserAgentMatcher that is currently in use
* Was the evaluated device found in the cache
* The installed branch of Tera-WURFL
* The installed version of Tera-WURFL
* The required version of PHP for this release
public static $required_php_version = "5.0.0";
* The array key that is returned as a WURFL capability group in the capabilities
* array that stored Tera-WURFL specific information about the request
* The Tera-WURFL specific data that is added to the capabilities array
* Array of UserAgentMatchers and match attempt types that the API used to find a matching device
* This keeps the device fallback lookup from running away.
* The deepest device I've seen is sonyericsson_z520a_subr3c at 15
if($this->db === false) $this->db = new $dbconnector;
if(!$this->db->connect()){
//throw new Exception("Cannot connect to database: ".$this->db->getLastError());
* Returns the matching WURFL ID for a given User Agent
* @return String WURFL ID
// Return generic UA if userAgent is empty
if(TeraWurflConfig::$SIMPLE_DESKTOP_ENGINE_ENABLE && $this->userAgent == WurflConstants::$SIMPLE_DESKTOP_UA){
// SimpleDesktop UA Matching avoids querying the database here
$deviceID = WurflConstants::$GENERIC_WEB_BROWSER;
$deviceID = $this->db->getDeviceFromUA($this->userAgent);
// Check for a conclusive match
$this->matchData['match_type'] = "conclusive";
// Check for Vodafone magic
@require_once realpath(dirname(__FILE__ ). '/UserAgentMatchers/VodafoneUserAgentMatcher.php');
$this->matcherHistory[] = $vodafoneUserAgentMatcher->matcherName() . "(conclusive)";
$deviceID = $vodafoneUserAgentMatcher->applyConclusiveMatch($this->userAgent);
$this->matchData['matcher'] = $vodafoneUserAgentMatcher->matcherName();
$this->matchData['match_type'] = "conclusive";
// Check for recovery match
// Check CatchAll if it's not already in use
$this->matcherHistory[] = $catchAllUserAgentMatcher->matcherName() . "(recovery)";
$deviceID = $catchAllUserAgentMatcher->applyRecoveryMatch($this->userAgent);
// The CatchAll matcher is intelligent enough to determine the match properties
$this->matchData['matcher'] = $catchAllUserAgentMatcher->matcher;
$this->matchData['match_type'] = $catchAllUserAgentMatcher->match_type;
$this->matchData['match'] = $catchAllUserAgentMatcher->match;
// A matching device still hasn't been found - check HTTP ACCEPT headers
WurflConstants::$ACCEPT_HEADER_XHTML_XML,
WurflConstants::$ACCEPT_HEADER_TEXT_HTML
// This isn't really a match, it's a suggestion
return WurflConstants::$GENERIC_WEB_BROWSER;
* Detects the capabilities from a given request object ($_SERVER)
* @param Array Request object ($_SERVER contains this data)
if(!isset ($server))$server = $_SERVER;
* Detects the capabilities of a device from a given user agent and optionally, the HTTP Accept Headers
* @param String HTTP User Agent
* @param String HTTP Accept Header
* @return Bool matching device was found
$this->db->numQueries = 0;
"actual_root_device" => '',
// Use the ultra high performance SimpleDesktopMatcher if enabled
require_once realpath(dirname(__FILE__ ). '/UserAgentMatchers/SimpleDesktopUserAgentMatcher.php');
// Define HTTP ACCEPT header. Default: DO NOT use HTTP_ACCEPT headers
//$this->httpAccept= (is_null($httpAccept))? WurflSupport::getAcceptHeader(): $httpAccept;
// Check cache for device
$cacheData = $this->db->getDeviceFromCache($this->userAgent);
if($cacheData !== false){
$deviceID = $cacheData['id'];
require_once realpath(dirname(__FILE__ ). '/UserAgentMatchers/SimpleDesktopUserAgentMatcher.php');
// Find appropriate user agent matcher
// Find the best matching WURFL ID
// Get the capabilities of this device and all its ancestors
// Now add in the Tera-WURFL results array
$this->matchData['num_queries'] = $this->db->numQueries;
// Add the match data to the capabilities array so it gets cached
// Since this device was not cached, cache it now.
* Builds the full capabilities array from the WURFL ID
// Now get all the devices in the fallback tree
$fallbackTree = $this->db->getDeviceFallBackTree($deviceID);
$fallbackTree = array_reverse($fallbackTree);
foreach($fallbackTree as $dev){
$fallbackIDs[] = $dev['id'];
if(isset ($dev['actual_device_root']) && $dev['actual_device_root'])$this->matchData['actual_root_device'] = $dev['id'];
$childDevice = $this->db->getDeviceFromID($deviceID);
$fallbackTree[] = $childDevice;
$fallbackIDs[] = $childDevice['id'];
$currentDevice = $childDevice;
* This loop starts with the best-matched device, and follows its fall_back until it reaches the GENERIC device
* Lets use "tmobile_shadow_ver1" for an example:
* 'id' => 'tmobile_shadow_ver1', 'fall_back' => 'ms_mobile_browser_ver1'
* 'id' => 'ms_mobile_browser_ver1', 'fall_back' => 'generic_xhtml'
* 'id' => 'generic_xhtml', 'fall_back' => 'generic'
* 'id' => 'generic', 'fall_back' => 'root'
* This fallback_tree in this example contains 4 elements in the order shown above.
while($currentDevice['fall_back'] != "root"){
$currentDevice = $this->db->getDeviceFromID($currentDevice['fall_back']);
if(in_array($currentDevice['id'],$fallbackIDs)){
// The device we just looked up is already in the list, which means that
// we are going to enter an infinate loop if we don't break from it.
$this->toLog("The device we just looked up is already in the list, which means that we are going to enter an infinate loop if we don't break from it. DeviceID: $deviceID, FallbackIDs: [". implode(',',$fallbackIDs). "]",LOG_ERR);
throw new Exception("Killed script to prevent infinate loop. See log for details.");
if(!isset ($currentDevice['fall_back']) || $currentDevice['fall_back'] == ''){
$this->toLog("Empty fall_back detected. DeviceID: $deviceID, FallbackIDs: [". implode(',',$fallbackIDs). "]",LOG_ERR);
throw new Exception("Empty fall_back detected. See log for details.");
$fallbackTree[] = $currentDevice;
$fallbackIDs[] = $currentDevice['id'];
$this->toLog("Exceeded maxDeviceDepth while trying to build capabilities for device. DeviceID: $deviceID, FallbackIDs: [". implode(',',$fallbackIDs). "]",LOG_ERR);
throw new Exception("Killed script to prevent infinate loop. See log for details.");
// The device we are looking up cannot be traced back to the GENERIC device
// and will likely not contain the correct capabilities
$this->toLog("The device we are looking up cannot be traced back to the GENERIC device and will likely not contain the correct capabilities. DeviceID: $deviceID, FallbackIDs: [". implode(',',$fallbackIDs). "]",LOG_ERR);
* Merge the device capabilities from the parent (GENERIC) to the child (DeviceID)
* We merge in this order because the GENERIC device contains all the properties that can be set
* Then the next child modifies them, then the next child, and the next child, etc...
while(count($fallbackTree)> 0){
// actual_root_device is the most accurate device in the fallback tree that is a "real" device, not a sub version or generic
if(isset ($dev['actual_device_root']) && $dev['actual_device_root'])$this->matchData['actual_root_device'] = $dev['id'];
* Returns the value of the requested capability for the detected device
* @param String Capability name (e.g. "is_wireless_device")
* @return Mixed Capability value
// TODO: Optimize function, one method is to flatten the capabilities array, or create a group=>cap index
$this->toLog('Searching for '. $capability. ' as a capability', LOG_INFO);
while ( list ($key, $value)= each($group) ) {
$this->toLog('I found it, value is '. $value, LOG_INFO);
$this->toLog('I could not find the requested capability ('. $capability. '), returning NULL', LOG_WARNING);
// since 1.5.2, I can't return "false" because that is a valid value. Now I return NULL, use is_null() to check
* Returns the value of the given setting name
* @param String Setting value
return $this->db->getSetting($key);
* Log an error in the Tera-WURFL log file
* @param String The error message text
* @param Int The log level / severity of the error
* @param String The function or code that was being run when the error occured
public function toLog($text, $requestedLogLevel= LOG_NOTICE, $func= "Tera-WURFL"){
if($requestedLogLevel == LOG_ERR) $this->errors[] = $text;
if (TeraWurflConfig::$LOG_LEVEL == 0 || ($requestedLogLevel- 1) >= TeraWurflConfig::$LOG_LEVEL ) {
if ( $requestedLogLevel == LOG_ERR ) {
$warn_banner = 'ERROR: ';
} else if ( $requestedLogLevel == LOG_WARNING ) {
$warn_banner = 'WARNING: ';
if(!is_writeable($logfile)){
throw new Exception("Tera-WURFL Error: cannot write to log file ($logfile)");
$_logFP = fopen($logfile, "a+");
fputs($_logFP, $_textToLog. "\n");
* Adds the top level properties to the capabilities array, like id and user_agent
* @param Array New properties to be added
foreach($newCapabilities as $key => $val){
* Add new capabilities to the capabilities array
* @param Array Capabilities that are to be added
self::mergeCapabilities($this->capabilities,$newCapabilities);
* Combines the MatcherHistory array into a string and stores it in the matchData
* Merges given $addedDevice array onto $baseDevice array
* @param Array Main capabilities array
* @param Array New capabilities array
if(count($baseDevice) == 0){
$baseDevice = $addedDevice;
foreach($addedDevice as $levOneKey => $levOneVal){
// Check if the base device has defined this value yet
// This is top level setting, not a capability
// This is an array value, merge the contents
foreach($levOneVal as $levTwoKey => $levTwoVal){
// This is just a scalar value, apply it
$baseDevice[$levOneKey][$levTwoKey] = $levTwoVal;
* Get the absolute path to the data directory on the filesystem
* @return String Absolute path to data directory
|