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

Source for file TeraWurfl.php

Documentation is available at TeraWurfl.php

  1. <?php
  2. /**
  3.  * Tera_WURFL - PHP MySQL driven WURFL
  4.  * 
  5.  * Tera-WURFL was written by Steve Kamerman, and is based on the
  6.  * Java WURFL Evolution package by Luca Passani and WURFL PHP Tools by Andrea Trassati.
  7.  * This version uses a MySQL database to store the entire WURFL file, multiple patch
  8.  * files, and a persistent caching mechanism to provide extreme performance increases.
  9.  * 
  10.  * @package TeraWurfl
  11.  * @author Steve Kamerman <stevekamerman AT gmail.com>
  12.  * @version Stable 2.1.2 $Date: 2010/05/14 15:53:02
  13.  * @license http://www.mozilla.org/MPL/ MPL Vesion 1.1
  14.  */
  15. /**#@+
  16.  * Include required files
  17.  */
  18. require_once realpath(dirname(__FILE__).'/TeraWurflConfig.php');
  19. require_once realpath(dirname(__FILE__).'/DatabaseConnectors/TeraWurflDatabase.php');
  20. require_once realpath(dirname(__FILE__).'/TeraWurflLoader.php');
  21. require_once realpath(dirname(__FILE__).'/UserAgentFactory.php');
  22. require_once realpath(dirname(__FILE__).'/UserAgentUtils.php');
  23. require_once realpath(dirname(__FILE__).'/WurflConstants.php');
  24. require_once realpath(dirname(__FILE__).'/WurflSupport.php');
  25. require_once realpath(dirname(__FILE__).'/UserAgentMatchers/UserAgentMatcher.php');
  26. /**#@-*/
  27. /**
  28.  * The main Tera-WURFL Class, provides all end-user methods and properties for interacting
  29.  * with Tera-WURFL
  30.  * 
  31.  * @package TeraWurfl
  32.  */
  33. class TeraWurfl{
  34.     
  35.     /**
  36.      * Array of errors that were encountered while processing the request
  37.      * @var Array 
  38.      */
  39.     public $errors;
  40.     /**
  41.      * Array of WURFL capabilities of the requested device
  42.      * @var Array 
  43.      */
  44.     public $capabilities;
  45.     /**
  46.      * Database connector to be used, must extend TeraWurflDatabase.  All database functions are performed
  47.      * in the database connector through its methods and properties.
  48.      * @see TeraWurflDatabase
  49.      * @see TeraWurflDatabase_MySQL5
  50.      * @var TeraWurflDatabase 
  51.      */
  52.     public $db = false;
  53.     /**
  54.      * The directory that TeraWurfl.php is in
  55.      * @var String 
  56.      */
  57.     public $rootdir;
  58.     public $tablename;
  59.     /**
  60.      * The user agent that is being evaluated
  61.      * @var String 
  62.      */
  63.     public $userAgent
  64.     /**
  65.      * The HTTP Accept header that is being evaluated
  66.      * @var String 
  67.      */
  68.     public $httpAccept;
  69.     /**
  70.      * The UserAgentMatcher that is currently in use
  71.      * @var UserAgentMatcher 
  72.      */
  73.     public $userAgentMatcher;
  74.     /**
  75.      * Was the evaluated device found in the cache
  76.      * @var Bool 
  77.      */
  78.     public $foundInCache;
  79.     
  80.     /**
  81.      * The installed branch of Tera-WURFL
  82.      * @var String 
  83.      */
  84.     public $release_branch = "Stable";
  85.     /**
  86.      * The installed version of Tera-WURFL
  87.      * @var String 
  88.      */
  89.     public $release_version = "2.1.2";
  90.     /**
  91.      * The required version of PHP for this release
  92.      * @var String 
  93.      */
  94.     public static $required_php_version "5.0.0";
  95.     
  96.     /**
  97.      * Lookup start time
  98.      * @var int 
  99.      */
  100.     protected $lookup_start;
  101.     /**
  102.      * Lookup end time
  103.      * @var int 
  104.      */
  105.     protected $lookup_end;
  106.     /**
  107.      * The array key that is returned as a WURFL capability group in the capabilities
  108.      * array that stored Tera-WURFL specific information about the request
  109.      * @var String 
  110.      */
  111.     protected $matchDataKey = "tera_wurfl";
  112.     /**
  113.      * The Tera-WURFL specific data that is added to the capabilities array
  114.      * @var array 
  115.      */
  116.     protected $matchData;
  117.     /**
  118.      * Array of UserAgentMatchers and match attempt types that the API used to find a matching device
  119.      * @var Array 
  120.      */
  121.     protected $matcherHistory;
  122.     /*
  123.      * This keeps the device fallback lookup from running away.
  124.      * The deepest device I've seen is sonyericsson_z520a_subr3c at 15
  125.      */
  126.     protected $maxDeviceDepth = 40;
  127.     
  128.     // Constructor
  129.     public function __construct(){
  130.         $this->errors = array();
  131.         $this->capabilities = array();
  132.         $this->matcherHistory = array();
  133.         $this->rootdir = dirname(__FILE__).'/';
  134.         $dbconnector 'TeraWurflDatabase_'.TeraWurflConfig::$DB_CONNECTOR;
  135.         if($this->db === false$this->db = new $dbconnector;
  136.         if(!$this->db->connect()){
  137.             //throw new Exception("Cannot connect to database: ".$this->db->getLastError());
  138.             return false;
  139.         }
  140.     }
  141.     
  142.     /**
  143.      * Returns the matching WURFL ID for a given User Agent
  144.      * @return String WURFL ID
  145.      */
  146.     protected function getDeviceIDFromUALoose(){
  147.         $this->matcherHistory = array();
  148.         // Return generic UA if userAgent is empty
  149.         if(strlen($this->userAgent)==0){
  150.             $this->matchData['matcher'"none"
  151.             $this->matchData['match_type'"none";
  152.             $this->matchData['match'false;
  153.             $this->setMatcherHistory();
  154.             return WurflConstants::$GENERIC;
  155.         }
  156.         // Set the table to be used for searching by the database
  157.         $this->db->tablename $this->fullTableName();
  158.         
  159.         // Check for exact match
  160.         if(TeraWurflConfig::$SIMPLE_DESKTOP_ENGINE_ENABLE && $this->userAgent == WurflConstants::$SIMPLE_DESKTOP_UA){
  161.             // SimpleDesktop UA Matching avoids querying the database here
  162.             $deviceID WurflConstants::$GENERIC_WEB_BROWSER;
  163.         }else{
  164.             $deviceID $this->db->getDeviceFromUA($this->userAgent);
  165.         }
  166.         $this->matcherHistory[$this->userAgentMatcher->matcherName("(exact)";
  167.         if($deviceID !== false){
  168.             $this->matchData['matcher'$this->userAgentMatcher->matcherName();
  169.             $this->matchData['match_type'"exact";
  170.             $this->matchData['match'true;
  171.             $this->setMatcherHistory();
  172.             return $deviceID;
  173.         }
  174.         // Check for a conclusive match
  175.         $deviceID $this->userAgentMatcher->applyConclusiveMatch($this->userAgent);
  176.         $this->matcherHistory[$this->userAgentMatcher->matcherName("(conclusive)";
  177.         if($deviceID != WurflConstants::$GENERIC){
  178.             $this->matchData['matcher'$this->userAgentMatcher->matcherName();
  179.             $this->matchData['match_type'"conclusive";
  180.             $this->matchData['match'true;
  181.             $this->setMatcherHistory();
  182.             return $deviceID;
  183.         }
  184.         // Check for Vodafone magic
  185.         if($this->userAgentMatcher->matcherName()!="VodafoneUserAgentMatcher" && UserAgentMatcher::contains($this->userAgent,"Vodafone")){
  186.             @require_once realpath(dirname(__FILE__).'/UserAgentMatchers/VodafoneUserAgentMatcher.php');
  187.             $vodafoneUserAgentMatcher new VodafoneUserAgentMatcher($this);
  188.             $this->matcherHistory[$vodafoneUserAgentMatcher->matcherName("(conclusive)";
  189.             $deviceID $vodafoneUserAgentMatcher->applyConclusiveMatch($this->userAgent);
  190.             if($deviceID != WurflConstants::$GENERIC){
  191.                 $this->matchData['matcher'$vodafoneUserAgentMatcher->matcherName();
  192.                 $this->matchData['match_type'"conclusive";
  193.                 $this->matchData['match'true;
  194.                 $this->setMatcherHistory();
  195.                 return $deviceID;
  196.             }
  197.         }
  198.         // Check for recovery match
  199.         $deviceID $this->userAgentMatcher->applyRecoveryMatch($this->userAgent);
  200.         $this->matcherHistory[$this->userAgentMatcher->matcherName("(recovery)";
  201.         if($deviceID != WurflConstants::$GENERIC){
  202.             $this->matchData['matcher'$this->userAgentMatcher->matcherName();
  203.             $this->matchData['match_type'"recovery";
  204.             $this->matchData['match'false;
  205.             $this->setMatcherHistory();
  206.             return $deviceID;
  207.         }
  208.         // Check CatchAll if it's not already in use
  209.         if($this->userAgentMatcher->matcherName()!="CatchAllUserAgentMatcher"){
  210.             $catchAllUserAgentMatcher new CatchAllUserAgentMatcher($this);
  211.             $this->matcherHistory[$catchAllUserAgentMatcher->matcherName("(recovery)";
  212.             $deviceID $catchAllUserAgentMatcher->applyRecoveryMatch($this->userAgent);
  213.             if($deviceID != WurflConstants::$GENERIC){
  214.                 // The CatchAll matcher is intelligent enough to determine the match properties
  215.                 $this->matchData['matcher'$catchAllUserAgentMatcher->matcher;
  216.                 $this->matchData['match_type'$catchAllUserAgentMatcher->match_type;
  217.                 $this->matchData['match'$catchAllUserAgentMatcher->match;
  218.                 $this->setMatcherHistory();
  219.                 return $deviceID;
  220.             }
  221.         }
  222.         
  223.         // A matching device still hasn't been found - check HTTP ACCEPT headers
  224.         if(strlen($this->httpAccept0){
  225.             $this->matcherHistory["http_accept";
  226.             if(UserAgentMatcher::contains($this->httpAccept,array(
  227.                 WurflConstants::$ACCEPT_HEADER_VND_WAP_XHTML_XML,
  228.                 WurflConstants::$ACCEPT_HEADER_XHTML_XML,
  229.                 WurflConstants::$ACCEPT_HEADER_TEXT_HTML
  230.               ))){
  231.                 $this->matchData['matcher'"http_accept";
  232.                 $this->matchData['match_type'"recovery";
  233.                 // This isn't really a match, it's a suggestion
  234.                 $this->matchData['match'false;
  235.                 $this->setMatcherHistory();
  236.                 return WurflConstants::$GENERIC_XHTML;
  237.             }
  238.         }
  239.         $this->matchData['matcher'"none";
  240.         $this->matchData['match_type'"none";
  241.         $this->matchData['match'false;
  242.         $this->setMatcherHistory();
  243.         
  244.         if(UserAgentUtils::isMobileBrowser($this->userAgent)) return WurflConstants::$GENERIC_XHTML;
  245.         return WurflConstants::$GENERIC_WEB_BROWSER;
  246.     }
  247.     /**
  248.      * Detects the capabilities from a given request object ($_SERVER)
  249.      * @param Array Request object ($_SERVER contains this data)
  250.      * @return Bool Match
  251.      */
  252.     public function getDeviceCapabilitiesFromRequest($server){
  253.         if(!isset($server))$server $_SERVER;
  254.         return getDeviceCapabilitiesFromAgent(WurflSupport::getUserAgent($server),WurflSupport::getAcceptHeader($server));
  255.     }
  256.     /**
  257.      * Detects the capabilities of a device from a given user agent and optionally, the HTTP Accept Headers
  258.      * @param String HTTP User Agent
  259.      * @param String HTTP Accept Header
  260.      * @return Bool matching device was found
  261.      */
  262.     public function getDeviceCapabilitiesFromAgent($userAgent=null,$httpAccept=null){
  263.         $this->db->numQueries 0;
  264.         $this->matchData = array(
  265.             "num_queries" => 0,
  266.             "actual_root_device" => '',
  267.             "match_type" => '',
  268.             "matcher" => '',
  269.             "match"    => false,
  270.             "lookup_time" => 0,
  271.             "fall_back_tree" => ''
  272.         );
  273.         $this->lookup_start = microtime(true);
  274.         $this->foundInCache = false;
  275.         $this->capabilities = array();
  276.         // Define User Agent
  277.         $this->userAgent = (is_null($userAgent))WurflSupport::getUserAgent()$userAgent;
  278.         if(strlen($this->userAgent255$this->userAgent = substr($this->userAgent,0,255);
  279.         // Use the ultra high performance SimpleDesktopMatcher if enabled
  280.         if(TeraWurflConfig::$SIMPLE_DESKTOP_ENGINE_ENABLE){
  281.             require_once realpath(dirname(__FILE__).'/UserAgentMatchers/SimpleDesktopUserAgentMatcher.php');
  282.             if(SimpleDesktopUserAgentMatcher::isDesktopBrowser($userAgent)) $this->userAgent = WurflConstants::$SIMPLE_DESKTOP_UA;
  283.         }
  284.         // Define HTTP ACCEPT header.  Default: DO NOT use HTTP_ACCEPT headers
  285.         //$this->httpAccept= (is_null($httpAccept))? WurflSupport::getAcceptHeader(): $httpAccept;
  286.         $this->tablename = TeraWurflConfig::$DEVICES;
  287.         $this->userAgent = UserAgentUtils::cleanUserAgent($this->userAgent);
  288.         // Check cache for device
  289.         if(TeraWurflConfig::$CACHE_ENABLE){
  290.             $cacheData $this->db->getDeviceFromCache($this->userAgent);
  291.             // Found in cache
  292.             if($cacheData !== false){
  293.                 $this->capabilities = $cacheData;
  294.                 $this->foundInCache = true;
  295.                 $deviceID $cacheData['id'];
  296.             }
  297.         }
  298.         if(!$this->foundInCache){
  299.             require_once realpath(dirname(__FILE__).'/UserAgentMatchers/SimpleDesktopUserAgentMatcher.php');
  300.             // Find appropriate user agent matcher
  301.             $this->userAgentMatcher = UserAgentFactory::createUserAgentMatcher($this,$this->userAgent);
  302.             // Find the best matching WURFL ID
  303.             $deviceID $this->getDeviceIDFromUALoose($userAgent);
  304.             // Get the capabilities of this device and all its ancestors
  305.             $this->getFullCapabilities($deviceID);
  306.             // Now add in the Tera-WURFL results array
  307.             $this->lookup_end = microtime(true);
  308.             $this->matchData['num_queries'$this->db->numQueries;
  309.             $this->matchData['lookup_time'$this->lookup_end - $this->lookup_start;
  310.             // Add the match data to the capabilities array so it gets cached
  311.             $this->addCapabilities(array($this->matchDataKey => $this->matchData));
  312.         }
  313.         if(TeraWurflConfig::$CACHE_ENABLE==true && !$this->foundInCache){
  314.             // Since this device was not cached, cache it now.
  315.             $this->db->saveDeviceInCache($this->userAgent,$this->capabilities);
  316.         }
  317.         return $this->capabilities[$this->matchDataKey]['match'];
  318.     }
  319.     /**
  320.      * Builds the full capabilities array from the WURFL ID
  321.      * @param String WURFL ID
  322.      * @return void 
  323.      */
  324.     public function getFullCapabilities($deviceID){
  325.         if(is_null($deviceID)){
  326.             throw new Exception("Invalid Device ID: ".var_export($deviceID,true)."\nMatcher: {$this->userAgentMatcher->matcherName()}\nUser Agent: ".$this->userAgent);
  327.             exit(1);
  328.         }
  329.         $this->db->tablename '';
  330.         // Now get all the devices in the fallback tree
  331.         $fallbackIDs array();
  332.         if($deviceID != WurflConstants::$GENERIC && $this->db->db_implements_fallback){
  333.             $fallbackTree $this->db->getDeviceFallBackTree($deviceID);
  334.             $this->addTopLevelSettings($fallbackTree[0]);
  335.             $fallbackTree array_reverse($fallbackTree);
  336.             foreach($fallbackTree as $dev){
  337.                 $fallbackIDs[$dev['id'];
  338.                 if(isset($dev['actual_device_root']&& $dev['actual_device_root'])$this->matchData['actual_root_device'$dev['id'];
  339.                 $this->addCapabilities($dev);
  340.             }
  341.             $this->matchData['fall_back_tree'implode(',',$fallbackIDs);
  342.         }else{
  343.             $fallbackTree array();
  344.             $childDevice $this->db->getDeviceFromID($deviceID);
  345.             $fallbackTree[$childDevice;
  346.             $fallbackIDs[$childDevice['id'];
  347.             $currentDevice $childDevice;
  348.             $i=0;
  349.             /**
  350.              * This loop starts with the best-matched device, and follows its fall_back until it reaches the GENERIC device
  351.              * Lets use "tmobile_shadow_ver1" for an example:
  352.              * 
  353.              * 'id' => 'tmobile_shadow_ver1', 'fall_back' => 'ms_mobile_browser_ver1'
  354.              * 'id' => 'ms_mobile_browser_ver1', 'fall_back' => 'generic_xhtml'
  355.              * 'id' => 'generic_xhtml', 'fall_back' => 'generic'
  356.              * 'id' => 'generic', 'fall_back' => 'root'
  357.              * 
  358.              * This fallback_tree in this example contains 4 elements in the order shown above.
  359.              * 
  360.              */
  361.             while($currentDevice['fall_back'!= "root"){
  362.                 $currentDevice $this->db->getDeviceFromID($currentDevice['fall_back']);
  363.                 if(in_array($currentDevice['id'],$fallbackIDs)){
  364.                     // The device we just looked up is already in the list, which means that
  365.                     // we are going to enter an infinate loop if we don't break from it.
  366.                     $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);
  367.                     throw new Exception("Killed script to prevent infinate loop.  See log for details.");
  368.                     break;
  369.                 }
  370.                 if(!isset($currentDevice['fall_back']|| $currentDevice['fall_back'== ''){
  371.                     $this->toLog("Empty fall_back detected. DeviceID: $deviceID, FallbackIDs: [".implode(',',$fallbackIDs)."]",LOG_ERR);
  372.                     throw new Exception("Empty fall_back detected.  See log for details.");
  373.                 }
  374.                 $fallbackTree[$currentDevice;
  375.                 $fallbackIDs[$currentDevice['id'];
  376.                 $i++;
  377.                 if($i $this->maxDeviceDepth){
  378.                     $this->toLog("Exceeded maxDeviceDepth while trying to build capabilities for device. DeviceID: $deviceID, FallbackIDs: [".implode(',',$fallbackIDs)."]",LOG_ERR);
  379.                     throw new Exception("Killed script to prevent infinate loop.  See log for details.");
  380.                     break;
  381.                 }
  382.             }
  383.             $this->matchData['fall_back_tree'implode(',',$fallbackIDs);
  384.             if($fallbackTree[count($fallbackTree)-1]['id'!= WurflConstants::$GENERIC){
  385.                 // The device we are looking up cannot be traced back to the GENERIC device
  386.                 // and will likely not contain the correct capabilities
  387.                 $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);
  388.             }
  389.             /**
  390.              * Merge the device capabilities from the parent (GENERIC) to the child (DeviceID)
  391.              * We merge in this order because the GENERIC device contains all the properties that can be set
  392.              * Then the next child modifies them, then the next child, and the next child, etc... 
  393.              */
  394.             while(count($fallbackTree)>0){
  395.                 $dev array_pop($fallbackTree);
  396.                 // actual_root_device is the most accurate device in the fallback tree that is a "real" device, not a sub version or generic
  397.                 if(isset($dev['actual_device_root']&& $dev['actual_device_root'])$this->matchData['actual_root_device'$dev['id'];
  398.                 $this->addCapabilities($dev);
  399.             }
  400.             $this->addTopLevelSettings($childDevice);
  401.         }
  402.     }
  403.     /**
  404.      * Returns the value of the requested capability for the detected device
  405.      * @param String Capability name (e.g. "is_wireless_device")
  406.      * @return Mixed Capability value
  407.      */
  408.     public function getDeviceCapability($capability{
  409.         // TODO: Optimize function, one method is to flatten the capabilities array, or create a group=>cap index
  410.         $this->toLog('Searching for '.$capability.' as a capability'LOG_INFO);
  411.         foreach $this->capabilities as $group {
  412.             if !is_array($group) ) {
  413.                 continue;
  414.             }
  415.             while list($key$value)=each($group) ) {
  416.                 if ($key==$capability{
  417.                     $this->toLog('I found it, value is '.$valueLOG_INFO);
  418.                     return $value;
  419.                 }
  420.             }
  421.         }
  422.         $this->toLog('I could not find the requested capability ('.$capability.'), returning NULL'LOG_WARNING);
  423.         // since 1.5.2, I can't return "false" because that is a valid value.  Now I return NULL, use is_null() to check
  424.         return null;
  425.     }
  426.     public function fullTableName(){
  427.         return $this->tablename.'_'.$this->userAgentMatcher->tableSuffix();
  428.     }
  429.     /**
  430.      * Log an error in the Tera-WURFL log file
  431.      * @see TeraWurflConfig
  432.      * @param String The error message text
  433.      * @param Int The log level / severity of the error
  434.      * @param String The function or code that was being run when the error occured
  435.      * @return void 
  436.      */
  437.     public function toLog($text$requestedLogLevel=LOG_NOTICE$func="Tera-WURFL"){
  438.         if($requestedLogLevel == LOG_ERR$this->errors[$text;
  439.         if (TeraWurflConfig::$LOG_LEVEL == || ($requestedLogLevel-1>= TeraWurflConfig::$LOG_LEVEL {
  440.             return;
  441.         }
  442.         if $requestedLogLevel == LOG_ERR {
  443.             $warn_banner 'ERROR: ';
  444.         else if $requestedLogLevel == LOG_WARNING {
  445.             $warn_banner 'WARNING: ';
  446.         else {
  447.             $warn_banner '';
  448.         }
  449.         $_textToLog date('r')." [".php_uname('n')." ".getmypid()."]"."[$func".$warn_banner $text;
  450.         $logfile $this->rootdir.TeraWurflConfig::$DATADIR.TeraWurflConfig::$LOG_FILE;
  451.         if(!is_writeable($logfile)){
  452.             throw new Exception("Tera-WURFL Error: cannot write to log file ($logfile)");
  453.         }
  454.         $_logFP fopen($logfile"a+");
  455.         fputs($_logFP$_textToLog."\n");
  456.         fclose($_logFP);
  457.     }
  458.     /**
  459.      * Adds the top level properties to the capabilities array, like id and user_agent
  460.      * @param Array New properties to be added
  461.      * @return void 
  462.      */
  463.     public function addTopLevelSettings(Array $newCapabilities){
  464.         foreach($newCapabilities as $key => $val){
  465.             if(is_array($val))continue;
  466.             $this->capabilities[$key$val;
  467.         }
  468.     }
  469.     /**
  470.      * Add new capabilities to the capabilities array
  471.      * @param Array Capabilities that are to be added
  472.      * @return void 
  473.      */
  474.     public function addCapabilities(Array $newCapabilities){
  475.         self::mergeCapabilities($this->capabilities,$newCapabilities);
  476.     
  477.     /**
  478.      * Combines the MatcherHistory array into a string and stores it in the matchData
  479.      * @return void 
  480.      */
  481.     protected function setMatcherHistory(){
  482.         $this->matchData['matcher_history'implode(',',$this->matcherHistory);
  483.     }
  484.     /**
  485.      * Merges given $addedDevice array onto $baseDevice array
  486.      * @param Array Main capabilities array
  487.      * @param Array New capabilities array
  488.      * @return void 
  489.      */
  490.     public static function mergeCapabilities(Array &$baseDeviceArray $addedDevice){
  491.         if(count($baseDevice== 0){
  492.             // Base device is empty
  493.             $baseDevice $addedDevice;
  494.             return;
  495.         }
  496.         foreach($addedDevice as $levOneKey => $levOneVal){
  497.             // Check if the base device has defined this value yet
  498.             if(!is_array($levOneVal)){
  499.                 // This is top level setting, not a capability
  500.                 continue;
  501.             }else{
  502.                 if(!array_key_exists($levOneKey,$baseDevice))$baseDevice[$levOneKey]=array();
  503.                 // This is an array value, merge the contents
  504.                 foreach($levOneVal as $levTwoKey => $levTwoVal){
  505.                     // This is just a scalar value, apply it
  506.                     $baseDevice[$levOneKey][$levTwoKey$levTwoVal;
  507.                     continue;
  508.                 }
  509.             }
  510.         }
  511.     }
  512.     /**
  513.      * Get the absolute path to the data directory on the filesystem
  514.      * @return String Absolute path to data directory
  515.      */
  516.     public static function absoluteDataDir(){
  517.         return dirname(__FILE__).'/'.TeraWurflConfig::$DATADIR;
  518.     }
  519. }

Documentation generated on Thu, 20 May 2010 20:20:25 +0000 by phpDocumentor 1.4.3