setCacheDir() method from your application to enable caching. * Use ->setCacheTTL() to set a time to live in minutes * * TODO: * Clean up the method lookup/validation interface. * Enforce a cache size limit * More complete token based authentication implementation * * REQUIREMENTS: * HTTP_Request from PEAR * SimpleXML (builtin) */ require_once "HTTP/Request.php"; // pear install HTTP_Request class UpcomingWrapper { const base_url = 'http://upcoming.yahooapis.com/services/rest/'; private $cache_dir; // directory to cache in private $cache_ttl = 60; // cache time to live in minutes private $cache = false; // is caching enabled? private $api_key; // api key private $method; // current method (validated) private $params; // parameter array private $requestType; // GET or POST? private $response; // Recordset or null for results private $responseCode; // HTTP response code private $error = false; // error state private $token; // to store a security token (if required) /* LOOKUP TABLE FOR METHOD REQUEST TYPES*/ private $getOrPost = array( "auth"=> HTTP_REQUEST_METHOD_GET, "event"=>array( "getInfo"=> HTTP_REQUEST_METHOD_GET, "search"=> HTTP_REQUEST_METHOD_GET, "getWatchList"=> HTTP_REQUEST_METHOD_GET, "getGroups"=> HTTP_REQUEST_METHOD_GET, "add"=> HTTP_REQUEST_METHOD_POST, "edit"=> HTTP_REQUEST_METHOD_POST, "addTags"=> HTTP_REQUEST_METHOD_POST, "removeTag"=> HTTP_REQUEST_METHOD_POST ), "metro"=> HTTP_REQUEST_METHOD_GET, "state"=> HTTP_REQUEST_METHOD_GET, "country"=> HTTP_REQUEST_METHOD_GET, "venue"=>array( "getInfo"=> HTTP_REQUEST_METHOD_GET, "getList"=> HTTP_REQUEST_METHOD_GET, "search"=> HTTP_REQUEST_METHOD_GET, "add"=> HTTP_REQUEST_METHOD_POST, "edit"=> HTTP_REQUEST_METHOD_POST ), "category"=> HTTP_REQUEST_METHOD_GET, "watchlist"=>array( "add"=> HTTP_REQUEST_METHOD_POST, "remove"=> HTTP_REQUEST_METHOD_POST), "user"=> HTTP_REQUEST_METHOD_GET, "group"=>array( "getInfo"=> HTTP_REQUEST_METHOD_GET, "getMembers"=> HTTP_REQUEST_METHOD_GET, "getEvents"=> HTTP_REQUEST_METHOD_GET, "getMyGroups"=> HTTP_REQUEST_METHOD_GET, "add"=> HTTP_REQUEST_METHOD_POST, "edit"=> HTTP_REQUEST_METHOD_POST, "join"=> HTTP_REQUEST_METHOD_POST, "leave"=> HTTP_REQUEST_METHOD_POST, "addEventTo"=> HTTP_REQUEST_METHOD_POST, "admin.removeEvent"=> HTTP_REQUEST_METHOD_POST ) ); /** All hail the Magic Methods! */ public function __construct($api_key) { $this->api_key = $api_key; } /** This method emulates PHP method calls into API calls. */ public function __call($name,$args) { $params = $args[0]; // should pass just one array $name = str_replace('_','.',$name); // changes _ notation into . notation $this->setMethodParams($name,$params); if ($this->sendRequest()) return $this->getResponse(); return null; } /** * The Next methods are signficiantly less magic, * and fairly self explainatory */ public function setMethodParams($method,$params) { // shortcut method $this->setMethod($method); $this->setParams($params); } public function setMethod($method) { return ($this->method = $this->verifyMethod($method)); } public function setParams($params) { if (is_array($params)) { // only accept parameters if they're an aray return ($this->params = $params); } return false; } public function setCacheDir($dir) { $this->cache = true; return ($this->cache_dir = $dir); } public function setCacheTTL($ttl) { return ($this->cache_ttl = $ttl); } public function getResponse() { return $this->response; } public function countResults() { return ($this->response != null) ? $this->response->countItems() : 0; } public function isError() { return $this->error; } public function getToken() { return $this->token; } public function setToken($token_obj) { return ($this->token = $token_obj); } /** * Take a frob (got from a callback somewhere presumabley) and get a security token with it. */ public function frobToToken($frob) { $client = new UpcomingWrapper($this->api_key); //aaaagghh recusion! $query = $client->auth_getToken(array($frob)); if ($client->isError()) return false; $result = $query->getResults(); $this->token = new UpcomingToken($result[0]); return true; } /** * Returns the URL needed to get the frob */ public function urlToGetFrob() { return "http://upcoming.yahoo.com/services/auth/?api_key={$this->api_key}"; } /** * Sends the request and processes results * If a cached result is available, this will bail to the checkCache method. */ public function sendRequest() { if ($this->error) { // if we have already errorerd $this->displayError(); return false; } $submit = $this->params; $submit['api_key'] = $this->api_key; $submit['method'] = $this->method; if ($this->token) // if we have a security token, use it regardless (should work right?) $submit['token'] = $this->token->token; if ($this->cache) { if ($this->checkCache()) // load a cached result if there is one .... return true; } // Prepare to send the request $req =& new HTTP_Request(self::base_url); $req->setMethod($type); // branch depending on request method if ($this->requestType == HTTP_REQUEST_METHOD_POST) { $req->addPostData = $submit; } else { // build a querystring from the parameters $querystring = self::base_url."?"; $submit = array_map("urlencode",$submit); // urlencode each element individually foreach ($submit as $key=>$item) { $querystring.= "$key=$item&"; } $querystring = rtrim($querystring,"&"); // remove trailing & $req->setURL($querystring); } // Send the request if (!PEAR::isError($req->sendRequest())) { $this->responseCode = $req->getResponseCode(); if ($this->responseCode == 200) { $this->parseResult($req->getResponseBody()); $this->makeCache(); return true; } } $this->parseError($req->getResponseBody()); $this->displayError(); $this->response = null; return false; } /* ** Works out what kind of HTTP Request Method the current API Method requires ** TODO: Make this less ugly :-P */ private function verifyMethod($method) { $methodPath = split('\.',$method); $value = $this->getOrPost; foreach ($methodPath as $elem) { $value = $value[$elem]; if (is_array($elem)) { continue; } // branch if ($value == HTTP_REQUEST_METHOD_GET || $value == HTTP_REQUEST_METHOD_POST) { // End of the tree break; } } if ($value != HTTP_REQUEST_METHOD_GET && $value != HTTP_REQUEST_METHOD_POST) { $this->error = true; $this->error_msg = 'Invalid method! See Upcoming API Documentation for valid methods'; return false; } $this->requestType = $value; return $method; } /** * Parses the result SimpleXMLObject into a Recordset Object which then populates itself accordingly */ private function parseResult($xml_response) { $xml = new SimpleXMLElement($xml_response); $this->response = new UpcomingRecordSet(); $this->response->simpleXmlToRecordSet($xml); $this->numberResults = $this->response->countItems(); } /* ** Parses the error XML and extracts the error message. */ private function parseError($response) { $parser = new SimpleXMLElement($response); $error = $parser->children()->attributes(); $this->error_msg = $error->msg; $this->error = true; } /* * Displays a pretty error message when it all goes horribly wrong */ private function displayError() { echo "

Utoh...

There's been a problem with your Upcoming.org API request.

"; if ($this->method) { echo "

Method: method}.php\">{$this->method}

"; } if (count($this->params) > 0) { echo "

Parameters:

"; } echo "

This generated a {$this->responseCode} code, with the error message:

{$this->error_msg}
"; } /* * Various methods to serials different parts of the object */ /* * Simply generates a hash to use as a plaintext identifier for the object * (say, a filename) */ private function getIdentifier() { return md5($this->serialiseQuery()); } /** * Stringifies the method and parameters. */ private function serialiseQuery() { foreach ($this->params as $key=>$value) { $params .= $key.':'.$value.'|'; } $params = rtrim($params,'|'); return "{$this->method}#$params"; } /** * Stringifies the Response object ready to be stored */ private function serialiseResponse() { return serialize($this->response); } /* * Cache management methods lie beyond * They are slightly mystical and rather ugly. */ /** * This method will load a cache if it can find a valid one, otherwise will return false * and (presumabley) the sendRequest method will continue to get the content, and then * have it cached for later */ private function checkCache() { $cachesize = 0; $identifier = $this->getIdentifier(); $dh = opendir($this->cache_dir); while($file = readdir($dh)) { if ($file != '.' && $file != '..') { $splitter = split('-',$file); $hashpart = $splitter[0]; if ($hashpart == $identifier) { // The query is the same .... if ($this->isCacheFileFresh($file)) { // the TTL is fresh ... return $this->readCache($file); } else { // use it or lose it. unlink($this->cache_dir.$file); } } } } return false; // no cache was loaded. } /** * Deletes any out of date cache files in an attempt to reclaim * space in the cache */ private function isCacheFileFresh($filename) { $splitter = split('-',$filename); $timepart = intval(rtrim($splitter[1],'.txt')); if (time() > ($timepart + ($this->cache_ttl*60))) return false; return true; } /** * Writes a cache of the current response object */ private function makeCache() { $fname = $this->getIdentifier().'-'.time().'.txt'; $fh = fopen($this->cache_dir.$fname,'w'); fwrite($fh,$this->serialiseResponse()); fclose($fh); } /** * Reads a cached file (relative to cache directory) * and places it into the response object field ready to be accessed */ private function readCache($filename) { if (file_exists($this->cache_dir.$filename)) { $this->response = unserialize( file_get_contents($this->cache_dir.$filename) ); if ($this->response != null) { return true; } } return false; } } class UpcomingRecordSet { /** TODO: Implement sorting/filtering methods. Implement iteration */ private $items = array(); // Holds records /** * Accepts SimpleXML object and maps it into several records */ public function simpleXmlToRecordSet($xml_object) { foreach ($xml_object->children() as $item) { $current = new UpcomingRecord(); $current->simpleXmlToRecord($item); $this->items[] = $current; } } /** * Returns the array of Records */ public function getResults() { return $this->items; } /** * Returns the number of items in the recordset */ public function countItems() { return count($this->items); } } class UpcomingRecord { private $attr = array(); // holds the attributes private $objectType; // holds the object type returned from the server /** * Takes a SimpleXML object and maps it into the $attr array for access and proper serialisation */ public function simpleXmlToRecord($xml_object) { $this->objectType = $xml_object->getName(); foreach ($xml_object->attributes() as $key=>$value) { $this->attr[$key] = (string)$value; // Cast to ensure we don't try to serialise SimpleXML (which cannot be unserialised) } } /** * Magical method to return attributes directly */ public function __get($name) { // magical method to get attributes return $this->attr[$name]; } /** * Returns the object type */ public function getType() { return $this->objectType; } } // Serialisable Token Storage Class class UpcomingToken { private $data = array(); public function __construct($token) { $this->simpleXmlToToken($token); } public function simpleXmlToToken($token) { foreach ($token as $key=>$value) { $data[$key] = (string) $value; } } public function __get($name) { return $data[$name]; } // class to store token data } ?>